Skip to content

Fix arrivals-and-departures returning bare "null" on unresolvable route refs#462

Merged
aaronbrethorst merged 1 commit into
mainfrom
fix/arrivals-null-route-bean-npe
Jun 24, 2026
Merged

Fix arrivals-and-departures returning bare "null" on unresolvable route refs#462
aaronbrethorst merged 1 commit into
mainfrom
fix/arrivals-null-route-bean-npe

Conversation

@aaronbrethorst

@aaronbrethorst aaronbrethorst commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary

  • A corrupt stop→route-collection index entry can resolve to a null RouteBean (_routeBeanService.getRouteForId() returns null per its contract). That null propagated into StopBean.getRoutes() and NPE'd in BeanFactoryV2.getStop() — which runs outside the action's try/catch — so the exception unwound out of show() with _response unset, and the ModelDriven serializer emitted a literal null body with HTTP 200.
  • On SDMTS this blanked 122 of 132 Blue Line (MTS_510) stops: the single-stop arrivals path builds references for up to 100 nearby stops, so one orphan-route stop poisoned every stop near it. Reproduces even with a zero-width time window; not realtime-related; bundle calendar covers today.
  • Fix drops the unresolvable route at the source, hardens the serializer, and makes the action fail loud (proper error envelope) instead of silently returning null.

Changes

  • StopBeanServiceImpl — skip unresolvable route beans on both the route-collection and static-route paths, with a WARN (instead of leaking a null into the stop's routes).
  • BeanFactoryV2.getStop — skip null RouteBean in the routes loop, mirroring the existing staticRoutes guard immediately below it.
  • StopWithArrivalsAndDeparturesBeanServiceImpl — skip null nearby StopBean (with a WARN).
  • ArrivalsAndDeparturesForStopAction.show() — wrap the V1/V2 bean conversion in a try/catch → setExceptionResponse(), so any future conversion failure returns a real error envelope rather than a bare null.
  • Tests — route-collection guard, static-route guard (StopBeanServiceImplTest), and serializer null-route guard (BeanFactoryV2Test).

The underlying bundle corruption (why a stop carries an unresolvable service-date route collection) is tracked separately in #461; this PR is the defensive/serialization fix so one bad record can't blank the API.

Test plan

  • StopBeanServiceImplTest — red→green for the route-collection orphan; new static-route orphan test; full beans package (24 tests) green
  • BeanFactoryV2Test — red (NPE, the bare-null cause) → green after the guard
  • onebusaway-api-webapp test-compile passes (action change compiles)
  • Not exercised against a live webapp + corrupt bundle — true end-to-end wasn't feasible locally; the unit tests model the exact production trigger (a single route-collection id resolving to null)

Review notes (non-blocking)

  • BeanFactoryV2.getStop guard is intentionally un-logged, matching the adjacent pre-existing silent staticRoutes guard and avoiding WARN spam on a hot path; the service-layer guard (which does log) is the primary trace.
  • Action try/catch is untested — the action's deps are private @Autowired with no setters, so a focused test would need a reflection-injection pattern new to this repo. Judged not worth it for a trivial log + setExceptionResponse() block.
  • Log volume: a persistently corrupt stop logs a WARN per request that touches it; acceptable as transient until SDMTS bundle: stop carries unresolvable service-date route-collection → null RouteBean (blanks Blue Line arrivals) #461 fixes the source data.

Summary by CodeRabbit

  • Bug Fixes

    • Resolved potential service failures caused by missing or null route data when processing stop information requests
    • Enhanced API error handling to gracefully manage unresolvable route references with improved diagnostic logging
  • Tests

    • Added comprehensive test coverage to verify proper null route handling and edge case scenarios

…te refs

A corrupt stop->route-collection index entry can resolve to a null RouteBean
(getRouteForId returns null). That null propagated into StopBean.getRoutes()
and NPEd in BeanFactoryV2.getStop(), which runs outside the action's try/catch,
so the exception unwound out of show() with _response unset and the ModelDriven
serializer emitted a literal "null" body with HTTP 200 -- blanking nearly the
entire SDMTS Blue Line via the nearby-stops reference build.

- StopBeanServiceImpl: drop unresolvable route beans (route-collection and
  static-route paths) with a WARN instead of leaking nulls
- BeanFactoryV2.getStop: skip null RouteBean, mirroring the existing
  staticRoutes guard
- StopWithArrivalsAndDeparturesBeanServiceImpl: skip null nearby StopBean
- ArrivalsAndDeparturesForStopAction: wrap bean conversion in try/catch so a
  failure returns a proper error envelope instead of a bare "null"
- Tests for the route-collection and static-route guards, and for the
  serializer null-route guard

Bundle data root cause tracked in #461.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds null guards across StopBeanServiceImpl (route and transfer resolution), StopWithArrivalsAndDeparturesBeanServiceImpl (nearby stop resolution), and BeanFactoryV2 (stop route iteration) to skip unresolvable beans and log warnings. ArrivalsAndDeparturesForStopAction.show() gains a try/catch around response construction. Unit tests cover the new null-skip paths.

Changes

Null Guards for Route and Stop Resolution

Layer / File(s) Summary
StopBeanServiceImpl null guards and logging
onebusaway-transit-data-federation/src/main/java/.../StopBeanServiceImpl.java
Adds SLF4J logger wiring and introduces null checks in fillTransfersForStopBean and fillRoutesForStopBean to log a warning and skip unresolvable route beans instead of adding nulls to the response list.
StopWithArrivalsAndDeparturesBeanServiceImpl null guard
onebusaway-transit-data-federation/src/main/java/.../StopWithArrivalsAndDeparturesBeanServiceImpl.java
Expands the nearbyStops loop to check for null before adding each resolved stop; logs a warning and skips unresolvable nearby stop IDs.
BeanFactoryV2 null guard
onebusaway-api-core/src/main/java/.../BeanFactoryV2.java
Adds a null check in the stop.getRoutes() iteration to skip null RouteBean entries before ID collection and reference registration.
ArrivalsAndDeparturesForStopAction exception handling
onebusaway-api-webapp/src/main/java/.../ArrivalsAndDeparturesForStopAction.java
Wraps the V1/V2 response-building block in show() with a try/catch; catches any exception, logs it with the stop id, and returns setExceptionResponse().
Unit tests for null-skip behavior
onebusaway-transit-data-federation/src/test/.../StopBeanServiceImplTest.java, onebusaway-api-core/src/test/.../BeanFactoryV2Test.java
Adds two StopBeanServiceImplTest tests covering unresolvable route collection IDs and unresolvable static routes, and adds BeanFactoryV2Test verifying a null RouteBean in the route list yields an empty routeIds set.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically identifies the main issue being fixed: the arrivals-and-departures endpoint returning a bare null response due to unresolvable route references.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/arrivals-null-route-bean-npe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/StopWithArrivalsAndDeparturesBeanServiceImpl.java (1)

149-166: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing null guard for nearby stop resolution in the multi-stop path.

Line 150 calls _stopBeanService.getStopForId(id, serviceInterval) and assigns the result to stop, but does not check for null before dereferencing it at lines 151–166. Based on the null check you added at lines 79–82 in the single-stop method above, getStopForId can return null when a stop does not resolve. If that happens here, the code will throw a NullPointerException when accessing stop.getLat() (line 153), stop.getRoutes() (line 159), or other methods.

Add the same defensive null check here as you did in the single-stop path.

🛡️ Proposed fix to guard against unresolvable nearby stops
     for (AgencyAndId id : allNearbyStopIds) {
       StopBean stop = _stopBeanService.getStopForId(id, serviceInterval);
+      if (stop == null) {
+        _log.warn("nearby stop {} did not resolve to a stop; skipping", id);
+        continue;
+      }
       if (center != null) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/StopWithArrivalsAndDeparturesBeanServiceImpl.java`
around lines 149 - 166, The for loop iterating over allNearbyStopIds calls
_stopBeanService.getStopForId(id, serviceInterval) but does not check if the
returned stop is null before dereferencing it in subsequent operations like
stop.getLat() and stop.getRoutes(). Add a null check immediately after the
getStopForId call within the loop, similar to the defensive null check you added
in the single-stop path above, and skip processing that iteration if stop is
null. This will prevent NullPointerException when getStopForId returns null for
an unresolvable stop.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/StopWithArrivalsAndDeparturesBeanServiceImpl.java`:
- Around line 149-166: The for loop iterating over allNearbyStopIds calls
_stopBeanService.getStopForId(id, serviceInterval) but does not check if the
returned stop is null before dereferencing it in subsequent operations like
stop.getLat() and stop.getRoutes(). Add a null check immediately after the
getStopForId call within the loop, similar to the defensive null check you added
in the single-stop path above, and skip processing that iteration if stop is
null. This will prevent NullPointerException when getStopForId returns null for
an unresolvable stop.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f386d7c-807c-4341-a624-13347c3ceef5

📥 Commits

Reviewing files that changed from the base of the PR and between 095bf1a and 801de7b.

📒 Files selected for processing (6)
  • onebusaway-api-core/src/main/java/org/onebusaway/api/model/transit/BeanFactoryV2.java
  • onebusaway-api-core/src/test/java/org/onebusaway/api/model/transit/BeanFactoryV2Test.java
  • onebusaway-api-webapp/src/main/java/org/onebusaway/api/actions/api/where/ArrivalsAndDeparturesForStopAction.java
  • onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/StopBeanServiceImpl.java
  • onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/StopWithArrivalsAndDeparturesBeanServiceImpl.java
  • onebusaway-transit-data-federation/src/test/java/org/onebusaway/transit_data_federation/impl/beans/StopBeanServiceImplTest.java

@aaronbrethorst aaronbrethorst merged commit 0780dbd into main Jun 24, 2026
2 of 3 checks passed
@aaronbrethorst aaronbrethorst deleted the fix/arrivals-null-route-bean-npe branch June 24, 2026 03:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants