# 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.