Detail Bug Report
https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_8c98bca4-beee-4bfb-b04d-e73e0e81aef4
Introduced in #34 by @WilliamAGH on Jan 23, 2026
Summary
- Context:
DirectionsResponse normalizes Apple Maps API responses. stepIndexes (from DirectionsRoute) contain integer values that index into the steps array.
- Bug: Null elements are filtered from
steps, but stepIndexes values are preserved unchanged. These indices reference positions in the original array, not the filtered array.
- Actual vs. expected:
stepIndexes values should remain valid indices into the normalized steps list, but filtering nulls shifts element positions, rendering indices invalid.
- Impact: Any consumer using
stepIndexes[i] to access steps.get(stepIndexes[i]) will get wrong steps or IndexOutOfBoundsException.
Code with Bug
// DirectionsResponse.java - filters nulls from steps
private static <T> List<T> normalizeList(List<T> rawList) {
if (rawList == null) {
return List.of();
}
return rawList.stream()
.filter(Objects::nonNull) // <-- BUG 🔴 filters nulls, shifting step positions and invalidating stepIndexes
.toList();
}
// DirectionsRoute.java - stepIndexes values are preserved unchanged
public DirectionsRoute {
// ...
stepIndexes = normalizeList(stepIndexes); // <-- BUG 🔴 only removes null entries from stepIndexes, does not adjust index values
}
Explanation
DirectionsRoute.stepIndexes stores index values into the DirectionsResponse.steps array as returned by the API (which can be sparse). When DirectionsResponse normalizes steps by removing null entries, it changes the positions of subsequent elements. Because stepIndexes values are not recomputed, they can become out-of-bounds or point to the wrong step.
Example:
- API:
steps = [step0, null, step2], stepIndexes = [0, 2]
- After normalization:
steps = [step0, step2] but stepIndexes remains [0, 2], so 2 is invalid for a list of size 2.
Codebase Inconsistency
A prior fix for stepPaths preserved index alignment with steps by converting nulls to placeholders instead of filtering them (commit 9989645). stepIndexes has the same “indexes into array” contract, but steps are still normalized via null filtering, so index alignment is not preserved.
Failing Test
// src/test/java/com/williamcallahan/applemaps/domain/model/DirectionsResponseIndexAlignmentBugTest.java
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
class DirectionsResponseIndexAlignmentBugTest {
@Test
void stepIndexesShouldRemainValidAfterStepsNormalization() {
DirectionsStep step0 = new DirectionsStep(
Optional.empty(), Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty()
);
DirectionsStep step2 = new DirectionsStep(
Optional.empty(), Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty()
);
List<DirectionsStep> stepsWithNull = new ArrayList<>();
stepsWithNull.add(step0);
stepsWithNull.add(null); // API returns sparse array
stepsWithNull.add(step2);
List<Integer> stepIndexes = List.of(0, 2); // Values reference original positions
DirectionsRoute route = new DirectionsRoute(
Optional.empty(), Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty(),
stepIndexes
);
DirectionsResponse response = new DirectionsResponse(
Optional.empty(), Optional.empty(), List.of(route), stepsWithNull, List.of()
);
for (Integer stepIndex : response.routes().get(0).stepIndexes()) {
assertTrue(stepIndex < response.steps().size());
}
}
}
Test output:
DirectionsResponseIndexAlignmentBugTest > stepIndexesShouldRemainValidAfterStepsNormalization() FAILED
org.opentest4j.AssertionFailedError: stepIndex 2 should be valid for steps list of size 2
==> expected: <true> but was: <false>
Recommended Fix
Preserve positional alignment in steps by converting null entries to an explicit placeholder (mirroring the stepPaths approach), instead of filtering:
private static List<DirectionsStep> normalizeSteps(List<DirectionsStep> rawList) {
if (rawList == null) {
return List.of();
}
return rawList.stream()
.map(step -> step != null ? step : DirectionsStep.EMPTY)
.toList();
}
History
This bug was introduced in commit e58b274. That change filtered null elements from lists to address sparse arrays, but did not account for index-aligned relationships between steps and stepIndexes. A later change (commit 9989645) preserved alignment for stepPaths ↔ steps, but the same fix pattern was not applied to steps ↔ stepIndexes.
Detail Bug Report
https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_8c98bca4-beee-4bfb-b04d-e73e0e81aef4
Introduced in #34 by @WilliamAGH on Jan 23, 2026
Summary
DirectionsResponsenormalizes Apple Maps API responses.stepIndexes(fromDirectionsRoute) contain integer values that index into thestepsarray.steps, butstepIndexesvalues are preserved unchanged. These indices reference positions in the original array, not the filtered array.stepIndexesvalues should remain valid indices into the normalizedstepslist, but filtering nulls shifts element positions, rendering indices invalid.stepIndexes[i]to accesssteps.get(stepIndexes[i])will get wrong steps orIndexOutOfBoundsException.Code with Bug
Explanation
DirectionsRoute.stepIndexesstores index values into theDirectionsResponse.stepsarray as returned by the API (which can be sparse). WhenDirectionsResponsenormalizesstepsby removing null entries, it changes the positions of subsequent elements. BecausestepIndexesvalues are not recomputed, they can become out-of-bounds or point to the wrong step.Example:
steps = [step0, null, step2],stepIndexes = [0, 2]steps = [step0, step2]butstepIndexesremains[0, 2], so2is invalid for a list of size2.Codebase Inconsistency
A prior fix for
stepPathspreserved index alignment withstepsby converting nulls to placeholders instead of filtering them (commit9989645).stepIndexeshas the same “indexes into array” contract, butstepsare still normalized via null filtering, so index alignment is not preserved.Failing Test
Test output:
Recommended Fix
Preserve positional alignment in
stepsby converting null entries to an explicit placeholder (mirroring thestepPathsapproach), instead of filtering:History
This bug was introduced in commit
e58b274. That change filtered null elements from lists to address sparse arrays, but did not account for index-aligned relationships betweenstepsandstepIndexes. A later change (commit9989645) preserved alignment forstepPaths↔steps, but the same fix pattern was not applied tosteps↔stepIndexes.