Skip to content

[Detail Bug] AppleMaps.resolveCompletionUrls wraps Error types instead of rethrowing #61

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_13fb1f2e-55cb-4552-aeb4-52b93af83c12

Summary

  • Context: The AppleMaps class provides a high-level resolveCompletionUrls method to asynchronously resolve multiple search completion URLs returned by the autocomplete API.
  • Bug: The exception handling in resolveCompletionUrls incorrectly wraps all non-RuntimeException throwables (such as java.lang.Error subclasses) in an AppleMapsClientException.
  • Actual vs. expected: When a task in the thread pool throws an Error (e.g., OutOfMemoryError), resolveCompletionUrls wraps it in a AppleMapsClientException instead of allowing the Error to propagate as-is.
  • Impact: Critical system failures are masked as minor client-side request failures, which can mislead developers and cause applications to incorrectly handle (or ignore) fatal errors.

Code with bug

00155|         try {
00156|             return futures.stream()
00157|                 .map(CompletableFuture::join)
00158|                 .toList();
00159|         } catch (Exception exception) {
00160|             Throwable cause = exception instanceof java.util.concurrent.CompletionException ? exception.getCause() : exception;
00161| 
00162|             if (cause instanceof RuntimeException runtimeException) {
00163|                 throw runtimeException;
00164|             }
00165|             throw new AppleMapsClientException("autocomplete", cause); // <-- BUG 🔴 Wraps Errors and other non-RuntimeExceptions
00166|         }

Failing test

A test that simulates an OutOfMemoryError during completion resolution demonstrates it is wrapped in AppleMapsClientException instead of being rethrown.

@Test
void testErrorWrapping() {
    AppleMapsGateway errorGateway = new AppleMapsGateway() {
        @Override
        public SearchResponse resolveCompletionUrl(String url) {
            throw new OutOfMemoryError("Fake OOM");
        }
        // ... other methods return null
    };

    AppleMaps maps = new AppleMaps(errorGateway);
    List<AutocompleteResult> results = List.of(
        new AutocompleteResult("url", List.of(), Optional.empty(), Optional.empty())
    );

    try {
        maps.resolveCompletionUrls(results);
        fail("Expected OutOfMemoryError to propagate");
    } catch (AppleMapsClientException e) {
        assertTrue(e.getCause() instanceof OutOfMemoryError);
    }
}

Observed: AppleMapsClientException("autocomplete") with cause OutOfMemoryError("Fake OOM").

Recommended fix

Update exception handling to rethrow Error and RuntimeException, and only wrap unexpected checked exceptions.

} catch (Exception exception) {
    Throwable cause = exception instanceof CompletionException ? exception.getCause() : exception;

    if (cause instanceof RuntimeException runtimeException) {
        throw runtimeException;
    }
    if (cause instanceof Error error) { // <-- FIX 🟢
        throw error;
    }
    throw new AppleMapsClientException("autocomplete", cause);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions