Describe the Bug
The core location-processing pipeline (processLocationEvent) throws java.lang.UnsupportedOperationException whenever a processing window produces no visits. mergeVisits returns an immutable List.of() for the empty case, and detectTrips then calls .sort(...) on that list, which immutable lists do not support. This aborts the whole processing run for the user.
The empty-visits case is normal and common: a window with no GPS data, sparse data, or where all points fall below the minimum-stay duration filter.
Root Cause
mergeVisits early-returns an immutable list:
// UnifiedLocationProcessingService.mergeVisits, ~L345
if (allVisits.isEmpty()) {
return new VisitMergingResult(List.of(), List.of(), searchStart, searchEnd, System.currentTimeMillis() - start);
}
processLocationEvent passes mergingResult.processedVisits straight into detectTrips (no copy), whose first action mutates it:
// UnifiedLocationProcessingService.detectTrips, L371
processedVisits.sort(Comparator.comparing(ProcessedVisit::getStartTime)); // UnsupportedOperationException on List.of()
The non-empty path builds processedVisits via mergeVisitsChronologically(...)/bulkInsert(...) (mutable), so only the empty path is affected.
Steps to Reproduce
- Trigger
processLocationEvent for a user/time-window that yields no detected visits (e.g. clustered-points query returns empty).
- Observe the pipeline abort.
Expected Behavior
With no visits, the pipeline completes (no trips created), no exception.
Observed Behavior
java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(...)
at …UnifiedLocationProcessingService.detectTrips(UnifiedLocationProcessingService.java:371)
Suggested Fix
Return a mutable list from the empty branch, or copy before sorting:
// option A — empty branch
return new VisitMergingResult(new ArrayList<>(), new ArrayList<>(), searchStart, searchEnd, ...);
// option B — detectTrips
List<ProcessedVisit> sorted = new ArrayList<>(processedVisits);
sorted.sort(Comparator.comparing(ProcessedVisit::getStartTime));
Corresponding Test (generated)
@Test
public void testProcessLocationEventWithEmptyVisits() {
// Arrange
User user = new User(1L, "testuser", "password", "Test User", null, null, com.dedicatedcode.reitti.model.Role.USER, 1L);
LocationProcessEvent event = new LocationProcessEvent("testuser", Instant.now().minusSeconds(3600), Instant.now(), null, null);
when(userJdbcService.findByUsername("testuser")).thenReturn(Optional.of(user));
// Mock detection parameters
DetectionParameter.VisitDetection visitDetection = new DetectionParameter.VisitDetection(60L, 300L);
DetectionParameter.VisitMerging visitMerging = new DetectionParameter.VisitMerging(1L, 1800L, 500L);
DetectionParameter mockConfig = new DetectionParameter(1L, visitDetection, visitMerging, null, Instant.now(), null);
when(visitDetectionParametersService.getCurrentConfiguration(any(User.class), any(Instant.class)))
.thenReturn(mockConfig);
// Mock existing visits
when(processedVisitJdbcService.findByUserAndStartTimeBeforeEqualAndEndTimeAfterEqual(
any(User.class), any(Instant.class), any(Instant.class)))
.thenReturn(new ArrayList<>());
// Mock clustered points with empty list
when(rawLocationPointJdbcService.findClusteredPointsInTimeRangeForUser(
any(User.class), any(Instant.class), any(Instant.class), anyInt(), anyDouble()))
.thenReturn(Collections.emptyList());
// Act
unifiedLocationProcessingService.processLocationEvent(event);
// Assert
verify(userJdbcService).findByUsername("testuser");
verify(visitDetectionParametersService).getCurrentConfiguration(any(User.class), any(Instant.class));
verify(rawLocationPointJdbcService).findClusteredPointsInTimeRangeForUser(
any(User.class), any(Instant.class), any(Instant.class), anyInt(), anyDouble());
verify(processedVisitJdbcService).findByUserAndStartTimeBeforeEqualAndEndTimeAfterEqual(
any(User.class), any(Instant.class), any(Instant.class));
}
This input was generated by the test case generator TestFusion developed in our STAR lab.
Describe the Bug
The core location-processing pipeline (
processLocationEvent) throwsjava.lang.UnsupportedOperationExceptionwhenever a processing window produces no visits.mergeVisitsreturns an immutableList.of()for the empty case, anddetectTripsthen calls.sort(...)on that list, which immutable lists do not support. This aborts the whole processing run for the user.The empty-visits case is normal and common: a window with no GPS data, sparse data, or where all points fall below the minimum-stay duration filter.
Root Cause
mergeVisitsearly-returns an immutable list:processLocationEventpassesmergingResult.processedVisitsstraight intodetectTrips(no copy), whose first action mutates it:The non-empty path builds
processedVisitsviamergeVisitsChronologically(...)/bulkInsert(...)(mutable), so only the empty path is affected.Steps to Reproduce
processLocationEventfor a user/time-window that yields no detected visits (e.g. clustered-points query returns empty).Expected Behavior
With no visits, the pipeline completes (no trips created), no exception.
Observed Behavior
Suggested Fix
Return a mutable list from the empty branch, or copy before sorting:
Corresponding Test (generated)
This input was generated by the test case generator
TestFusiondeveloped in our STAR lab.