# Apple Maps Server SDK for Java Apple Maps Java SDK is a lightweight, unofficial Java library for interacting with the Apple Maps Server API. It provides typed wrappers for server-side geocoding, search, autocomplete, directions, and ETA operations, making it ideal for backend JVM applications that need location services without a UI component. The SDK automatically handles token exchange, converting your long-lived Apple-issued JWT authorization token into short-lived access tokens and refreshing them as needed. This SDK requires Java 17+ and an Apple Developer Program membership to obtain a Maps Identifier and private key. It works with any modern JVM application including Spring Boot, Quarkus, and plain Java, but is not compatible with Android (uses `java.net.http.HttpClient`) or Kotlin Multiplatform/iOS targets. The library is published on Maven Central under the coordinates `com.williamcallahan:apple-maps-java`. ## Installation Add the dependency to your project using Gradle or Maven. ```groovy // Gradle dependencies { implementation("com.williamcallahan:apple-maps-java:0.1.4") } ``` ```xml com.williamcallahan apple-maps-java 0.1.4 ``` ## Client Initialization The AppleMaps client is the main entry point for all API operations. It handles authentication and provides typed methods for each Apple Maps Server API endpoint. ```java import com.williamcallahan.applemaps.AppleMaps; // Basic initialization with token only String token = System.getenv("APPLE_MAPS_TOKEN"); AppleMaps api = new AppleMaps(token); // With origin header (required for some JWT configurations) String origin = System.getenv("APPLE_MAPS_ORIGIN"); AppleMaps apiWithOrigin = new AppleMaps(token, origin); // With custom timeout import java.time.Duration; AppleMaps apiWithTimeout = new AppleMaps(token, Duration.ofSeconds(30), origin); // Always close when done (implements AutoCloseable) try (AppleMaps client = new AppleMaps(token)) { // Use client } ``` ## Geocode Transform a street address into coordinates and a structured address. The geocode API is optimized for nearly complete addresses rather than fuzzy queries. ```java import com.williamcallahan.applemaps.AppleMaps; import com.williamcallahan.applemaps.domain.model.PlaceResults; import com.williamcallahan.applemaps.domain.model.UserLocation; import com.williamcallahan.applemaps.domain.request.GeocodeInput; AppleMaps api = new AppleMaps(System.getenv("APPLE_MAPS_TOKEN")); // Basic geocode PlaceResults results = api.geocode( GeocodeInput.builder("880 Harrison St, San Francisco, CA 94107").build() ); // With location hint for better relevance PlaceResults resultsWithHint = api.geocode( GeocodeInput.builder("123 Main St") .userLocation(UserLocation.fromLatitudeLongitude(37.7796095, -122.4016725)) .limitToCountries(List.of("US")) .language("en-US") .build() ); // Access results results.results().forEach(place -> { System.out.println("Name: " + place.name()); System.out.println("Coordinates: " + place.coordinate().latitude() + ", " + place.coordinate().longitude()); System.out.println("Address: " + String.join(", ", place.formattedAddressLines())); System.out.println("Country: " + place.countryCode()); }); // Example output: // { // "results": [{ // "name": "880 Harrison St", // "coordinate": { "latitude": 37.7796095, "longitude": -122.4016725 }, // "formattedAddressLines": ["880 Harrison St", "San Francisco, CA 94107", "United States"], // "structuredAddress": { // "locality": "San Francisco", // "postCode": "94107", // "thoroughfare": "Harrison St", // "subThoroughfare": "880" // }, // "countryCode": "US" // }] // } ``` ## Reverse Geocode Transform coordinates into a structured address. Useful for converting GPS coordinates to human-readable locations. ```java import com.williamcallahan.applemaps.AppleMaps; import com.williamcallahan.applemaps.domain.model.PlaceResults; AppleMaps api = new AppleMaps(System.getenv("APPLE_MAPS_TOKEN")); // Basic reverse geocode (defaults to en-US) PlaceResults results = api.reverseGeocode(37.7796095, -122.4016725); // With specific language PlaceResults resultsInGerman = api.reverseGeocode(53.551666, 9.9942163, "de-DE"); // Access the address results.results().forEach(place -> { System.out.println("Address: " + String.join(", ", place.formattedAddressLines())); if (place.structuredAddress() != null) { System.out.println("City: " + place.structuredAddress().locality()); System.out.println("Street: " + place.structuredAddress().fullThoroughfare()); } }); ``` ## Search Search for places including addresses and points of interest (POIs). Better suited for fuzzy queries than geocode. ```java import com.williamcallahan.applemaps.AppleMaps; import com.williamcallahan.applemaps.domain.model.SearchResponse; import com.williamcallahan.applemaps.domain.model.UserLocation; import com.williamcallahan.applemaps.domain.model.PoiCategory; import com.williamcallahan.applemaps.domain.model.SearchResultType; import com.williamcallahan.applemaps.domain.request.SearchInput; AppleMaps api = new AppleMaps(System.getenv("APPLE_MAPS_TOKEN")); // Basic search SearchResponse response = api.search( SearchInput.builder("coffee").build() ); // Search with location hint and filters SearchResponse filteredResponse = api.search( SearchInput.builder("coffee shops") .userLocation(UserLocation.fromLatitudeLongitude(37.7796095, -122.4016725)) .limitToCountries(List.of("US")) .includePoiCategories(List.of(PoiCategory.CAFE, PoiCategory.RESTAURANT)) .resultTypeFilter(List.of(SearchResultType.POI)) .language("en-US") .enablePagination(true) .build() ); // Search for a business SearchResponse businessSearch = api.search( SearchInput.builder("Stripe San Francisco") .userLocation(UserLocation.fromLatitudeLongitude(37.7796095, -122.4016725)) .build() ); // Access results response.results().forEach(place -> { System.out.println("Name: " + place.name()); System.out.println("Category: " + place.poiCategory()); System.out.println("Address: " + String.join(", ", place.formattedAddressLines())); }); ``` ## Autocomplete Get a list of address and POI completions for a fuzzy prompt. Ideal for typeahead search interfaces. ```java import com.williamcallahan.applemaps.AppleMaps; import com.williamcallahan.applemaps.domain.model.SearchAutocompleteResponse; import com.williamcallahan.applemaps.domain.model.SearchResponse; import com.williamcallahan.applemaps.domain.model.UserLocation; import com.williamcallahan.applemaps.domain.request.SearchAutocompleteInput; import java.util.List; AppleMaps api = new AppleMaps(System.getenv("APPLE_MAPS_TOKEN")); // Basic autocomplete SearchAutocompleteResponse autocomplete = api.autocomplete( SearchAutocompleteInput.builder("Apple Par").build() ); // With location hint SearchAutocompleteResponse localAutocomplete = api.autocomplete( SearchAutocompleteInput.builder("Dorfstr") .userLocation(UserLocation.fromLatitudeLongitude(53.551666, 9.9942163)) .limitToCountries(List.of("DE")) .build() ); // Access completion results autocomplete.results().forEach(result -> { System.out.println("Display: " + result.displayLines()); System.out.println("Completion URL: " + result.completionUrl()); }); // Resolve a single completion URL to get full details SearchResponse resolved = api.resolveCompletionUrl( autocomplete.results().get(0).completionUrl() ); // Resolve all completion URLs in parallel List allResolved = api.resolveCompletionUrls(autocomplete); allResolved.forEach(searchResponse -> { searchResponse.results().forEach(place -> { System.out.println("Resolved: " + place.name() + " - " + place.formattedAddressLines()); }); }); ``` ## Directions Request routes between coordinates or addresses. Supports multiple transport types and route options. ```java import com.williamcallahan.applemaps.AppleMaps; import com.williamcallahan.applemaps.domain.model.DirectionsEndpoint; import com.williamcallahan.applemaps.domain.model.DirectionsResponse; import com.williamcallahan.applemaps.domain.model.DirectionsAvoid; import com.williamcallahan.applemaps.domain.model.TransportType; import com.williamcallahan.applemaps.domain.request.DirectionsInput; import java.util.List; AppleMaps api = new AppleMaps(System.getenv("APPLE_MAPS_TOKEN")); // Directions between coordinates DirectionsEndpoint origin = DirectionsEndpoint.fromLatitudeLongitude(53.551666, 9.9942163); DirectionsEndpoint destination = DirectionsEndpoint.fromLatitudeLongitude(53.558, 10.0); DirectionsResponse response = api.directions( DirectionsInput.builder(origin, destination).build() ); // Directions between addresses DirectionsEndpoint originAddress = DirectionsEndpoint.fromAddress("880 Harrison St, San Francisco, CA"); DirectionsEndpoint destAddress = DirectionsEndpoint.fromAddress("1 Infinite Loop, Cupertino, CA"); DirectionsResponse addressResponse = api.directions( DirectionsInput.builder(originAddress, destAddress) .transportType(TransportType.AUTOMOBILE) .avoid(List.of(DirectionsAvoid.TOLLS)) .requestsAlternateRoutes(true) .language("en-US") .build() ); // Access route information response.routes().forEach(route -> { System.out.println("Distance: " + route.distanceMeters() + " meters"); System.out.println("Duration: " + route.expectedTravelTimeSeconds() + " seconds"); route.steps().forEach(step -> { System.out.println(" - " + step.instructions()); }); }); ``` ## ETA (Estimated Time of Arrival) Request travel time estimates from one origin to multiple destinations (up to 10). ```java import com.williamcallahan.applemaps.AppleMaps; import com.williamcallahan.applemaps.domain.model.EtaResponse; import com.williamcallahan.applemaps.domain.model.RouteLocation; import com.williamcallahan.applemaps.domain.model.TransportType; import com.williamcallahan.applemaps.domain.request.EtaInput; import java.util.List; AppleMaps api = new AppleMaps(System.getenv("APPLE_MAPS_TOKEN")); // Define origin and destinations RouteLocation origin = RouteLocation.fromLatitudeLongitude(53.551666, 9.9942163); List destinations = List.of( RouteLocation.fromLatitudeLongitude(53.558, 10.0), RouteLocation.fromLatitudeLongitude(53.565, 10.1), RouteLocation.fromLatitudeLongitude(53.570, 10.2) ); // Request ETAs EtaResponse response = api.etas( EtaInput.builder(origin, destinations).build() ); // With transport type EtaResponse walkingEta = api.etas( EtaInput.builder(origin, destinations) .transportType(TransportType.WALKING) .build() ); // Access ETA estimates response.etas().forEach(eta -> { System.out.println("Destination: " + eta.destination()); System.out.println("Travel time: " + eta.expectedTravelTimeSeconds() + " seconds"); System.out.println("Distance: " + eta.distanceMeters() + " meters"); }); ``` ## Place Lookup Look up a place by its identifier or perform batch lookups for multiple places. ```java import com.williamcallahan.applemaps.AppleMaps; import com.williamcallahan.applemaps.domain.model.Place; import com.williamcallahan.applemaps.domain.model.PlacesResponse; import com.williamcallahan.applemaps.domain.request.PlaceLookupInput; import java.util.List; AppleMaps api = new AppleMaps(System.getenv("APPLE_MAPS_TOKEN")); // Single place lookup (defaults to en-US) Place place = api.lookupPlace("I7AB53F714DE0BD8B"); // With specific language Place placeInGerman = api.lookupPlace("I7AB53F714DE0BD8B", "de-DE"); // Batch lookup PlacesResponse places = api.lookupPlaces( PlaceLookupInput.builder(List.of("placeIdA", "placeIdB", "placeIdC")) .language("en-US") .build() ); // Access place details System.out.println("Name: " + place.name()); System.out.println("Address: " + String.join(", ", place.formattedAddressLines())); System.out.println("Coordinates: " + place.coordinate().latitude() + ", " + place.coordinate().longitude()); ``` ## Alternate IDs Resolve alternate identifiers for places. Useful for cross-referencing places across different systems. ```java import com.williamcallahan.applemaps.AppleMaps; import com.williamcallahan.applemaps.domain.model.AlternateIdsResponse; import com.williamcallahan.applemaps.domain.request.AlternateIdsInput; import java.util.List; AppleMaps api = new AppleMaps(System.getenv("APPLE_MAPS_TOKEN")); // Look up alternate IDs AlternateIdsResponse response = api.lookupAlternateIds( AlternateIdsInput.builder(List.of("placeIdA", "placeIdB")).build() ); // Access alternate IDs response.results().forEach(entry -> { System.out.println("Original ID: " + entry.id()); entry.alternateIds().forEach(altId -> { System.out.println(" Alternate: " + altId); }); }); ``` ## CLI Usage The SDK includes a command-line interface for quick testing and one-off queries. Set your token as an environment variable and run commands via Gradle. ```bash # Set token export APPLE_MAPS_TOKEN="your-token" # Optional: set origin if required by your JWT export APPLE_MAPS_ORIGIN="https://api.example.com" # Geocode ./gradlew cli --args='geocode "880 Harrison St, San Francisco, CA 94107"' ./gradlew cli --args='geocode "880 Harrison St, San Francisco, CA 94107" --json' # Reverse geocode ./gradlew cli --args='reverse-geocode 37.7796095 -122.4016725' # Search ./gradlew cli --args='search "coffee"' ./gradlew cli --args='search "Stripe" --limit-to-countries US' # Autocomplete ./gradlew cli --args='autocomplete "Apple Park"' ./gradlew cli --args='autocomplete "Apple Park" --api-url' # Resolve completion URL ./gradlew cli --args='resolve "https://maps-api.apple.com/v1/search?..." --json' # With location bias export APPLE_MAPS_USER_LOCATION="37.7796095,-122.4016725" ./gradlew cli --args='search "coffee"' # Or with natural language location (adds one geocode call) export APPLE_MAPS_USER_LOCATION_QUERY="San Francisco, CA" ./gradlew cli --args='search "coffee"' ``` ## Summary The Apple Maps Java SDK is well-suited for backend services that need geocoding, location search, directions, and ETA calculations without requiring a UI component. Common use cases include finding businesses by name or partial address (using Search + Autocomplete), converting addresses to coordinates for database storage, calculating delivery routes and ETAs, and building location-aware search features. The SDK's automatic token management and parallel completion URL resolution make it efficient for high-throughput applications. Integration is straightforward through Maven Central dependencies and follows standard Java patterns with builder-style APIs and record types. For best results, always provide location hints (userLocation, searchLocation, or searchRegion) when available to improve relevance, use Autocomplete for typeahead interfaces rather than Geocode, and be mindful of Apple's daily quota limits shared across MapKit JS and the Server API. The SDK implements AutoCloseable, so use try-with-resources or explicitly close the client when finished.