The Google Maps Distance Matrix API is a powerful service that provides travel distance and time for a matrix of origins and destinations. It's incredibly useful for logistics, route planning, and applications that need to calculate multiple distances at once. In this article, we'll build a robust Java client to interact with this API.
Understanding the API
The Distance Matrix API:
- Calculates travel distance and time between multiple origins and destinations
- Supports various travel modes: driving, walking, bicycling, transit
- Provides data for different transit modes and traffic models
- Returns results in JSON or XML format
Project Setup
First, add the necessary dependencies to your pom.xml:
<dependencies> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.15.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- For logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.7</version> </dependency> </dependencies>
Java Implementation
1. Configuration Class
ApiConfig.java - Stores API configuration and constants
public class ApiConfig {
private final String apiKey;
private final String baseUrl;
public ApiConfig(String apiKey) {
this.apiKey = apiKey;
this.baseUrl = "https://maps.googleapis.com/maps/api/distancematrix/json";
}
public String getApiKey() { return apiKey; }
public String getBaseUrl() { return baseUrl; }
}
2. Request Models
TravelMode.java - Enum for different travel modes
public enum TravelMode {
DRIVING("driving"),
WALKING("walking"),
BICYCLING("bicycling"),
TRANSIT("transit");
private final String mode;
TravelMode(String mode) {
this.mode = mode;
}
public String getMode() { return mode; }
}
TransitMode.java - Enum for transit options (optional)
public enum TransitMode {
BUS("bus"),
SUBWAY("subway"),
TRAIN("train"),
TRAM("tram"),
RAIL("rail");
private final String mode;
TransitMode(String mode) {
this.mode = mode;
}
public String getMode() { return mode; }
}
DistanceMatrixRequest.java - Builder pattern for constructing requests
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class DistanceMatrixRequest {
private final List<String> origins;
private final List<String> destinations;
private TravelMode mode;
private String language;
private String units;
private String arrivalTime;
private String departureTime;
private List<TransitMode> transitModes;
private String trafficModel;
private boolean avoidTolls;
private boolean avoidHighways;
private boolean avoidFerries;
private DistanceMatrixRequest(Builder builder) {
this.origins = builder.origins;
this.destinations = builder.destinations;
this.mode = builder.mode;
this.language = builder.language;
this.units = builder.units;
this.arrivalTime = builder.arrivalTime;
this.departureTime = builder.departureTime;
this.transitModes = builder.transitModes;
this.trafficModel = builder.trafficModel;
this.avoidTolls = builder.avoidTolls;
this.avoidHighways = builder.avoidHighways;
this.avoidFerries = builder.avoidFerries;
}
// Builder class
public static class Builder {
private final List<String> origins = new ArrayList<>();
private final List<String> destinations = new ArrayList<>();
private TravelMode mode = TravelMode.DRIVING;
private String language = "en";
private String units = "metric";
private String arrivalTime;
private String departureTime = "now";
private List<TransitMode> transitModes;
private String trafficModel;
private boolean avoidTolls = false;
private boolean avoidHighways = false;
private boolean avoidFerries = false;
public Builder addOrigin(String origin) {
this.origins.add(origin);
return this;
}
public Builder addOrigins(List<String> origins) {
this.origins.addAll(origins);
return this;
}
public Builder addDestination(String destination) {
this.destinations.add(destination);
return this;
}
public Builder addDestinations(List<String> destinations) {
this.destinations.addAll(destinations);
return this;
}
public Builder withMode(TravelMode mode) {
this.mode = mode;
return this;
}
public Builder withLanguage(String language) {
this.language = language;
return this;
}
public Builder withUnits(String units) {
this.units = units;
return this;
}
public Builder withDepartureTime(String departureTime) {
this.departureTime = departureTime;
return this;
}
public Builder withTransitModes(List<TransitMode> transitModes) {
this.transitModes = transitModes;
return this;
}
public DistanceMatrixRequest build() {
if (origins.isEmpty() || destinations.isEmpty()) {
throw new IllegalStateException("Origins and destinations must not be empty");
}
return new DistanceMatrixRequest(this);
}
}
// Getters
public List<String> getOrigins() { return origins; }
public List<String> getDestinations() { return destinations; }
public TravelMode getMode() { return mode; }
public String getLanguage() { return language; }
public String getUnits() { return units; }
public String getArrivalTime() { return arrivalTime; }
public String getDepartureTime() { return departureTime; }
public List<TransitMode> getTransitModes() { return transitModes; }
public String getTrafficModel() { return trafficModel; }
public boolean isAvoidTolls() { return avoidTolls; }
public boolean isAvoidHighways() { return avoidHighways; }
public boolean isAvoidFerries() { return avoidFerries; }
public String toQueryString() {
StringBuilder sb = new StringBuilder();
// Add origins
sb.append("origins=").append(String.join("|", origins));
// Add destinations
sb.append("&destinations=").append(String.join("|", destinations));
// Add mode
sb.append("&mode=").append(mode.getMode());
// Add language
sb.append("&language=").append(language);
// Add units
sb.append("&units=").append(units);
// Add departure time (required for transit mode)
if (departureTime != null) {
sb.append("&departure_time=").append(departureTime);
}
// Add transit modes if specified
if (transitModes != null && !transitModes.isEmpty()) {
String modes = transitModes.stream()
.map(TransitMode::getMode)
.collect(Collectors.joining("|"));
sb.append("&transit_mode=").append(modes);
}
// Add avoidance parameters
List<String> avoidances = new ArrayList<>();
if (avoidTolls) avoidances.add("tolls");
if (avoidHighways) avoidances.add("highways");
if (avoidFerries) avoidances.add("ferries");
if (!avoidances.isEmpty()) {
sb.append("&avoid=").append(String.join("|", avoidances));
}
return sb.toString();
}
}
3. Response Models
DistanceMatrixResponse.java - Main response object
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class DistanceMatrixResponse {
@JsonProperty("origin_addresses")
private List<String> originAddresses;
@JsonProperty("destination_addresses")
private List<String> destinationAddresses;
private List<Row> rows;
private String status;
@JsonProperty("error_message")
private String errorMessage;
// Getters and setters
public List<String> getOriginAddresses() { return originAddresses; }
public void setOriginAddresses(List<String> originAddresses) { this.originAddresses = originAddresses; }
public List<String> getDestinationAddresses() { return destinationAddresses; }
public void setDestinationAddresses(List<String> destinationAddresses) { this.destinationAddresses = destinationAddresses; }
public List<Row> getRows() { return rows; }
public void setRows(List<Row> rows) { this.rows = rows; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getErrorMessage() { return errorMessage; }
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
}
Row.java - Represents a row in the distance matrix
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Row {
private List<Element> elements;
public List<Element> getElements() { return elements; }
public void setElements(List<Element> elements) { this.elements = elements; }
}
Element.java - Individual distance/time element
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Element {
private Distance distance;
private Duration duration;
@JsonProperty("duration_in_traffic")
private Duration durationInTraffic;
private String status;
// Getters and setters
public Distance getDistance() { return distance; }
public void setDistance(Distance distance) { this.distance = distance; }
public Duration getDuration() { return duration; }
public void setDuration(Duration duration) { this.duration = duration; }
public Duration getDurationInTraffic() { return durationInTraffic; }
public void setDurationInTraffic(Duration durationInTraffic) { this.durationInTraffic = durationInTraffic; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}
Distance.java - Distance information
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Distance {
private String text;
private int value;
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public int getValue() { return value; }
public void setValue(int value) { this.value = value; }
}
Duration.java - Duration information
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Duration {
private String text;
private int value;
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public int getValue() { return value; }
public void setValue(int value) { this.value = value; }
}
4. Core Service Class
DistanceMatrixService.java - Main service class for API interactions
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class DistanceMatrixService {
private static final Logger logger = LoggerFactory.getLogger(DistanceMatrixService.class);
private final ApiConfig config;
private final CloseableHttpClient httpClient;
private final ObjectMapper objectMapper;
public DistanceMatrixService(ApiConfig config) {
this.config = config;
this.httpClient = HttpClients.createDefault();
this.objectMapper = new ObjectMapper();
}
public DistanceMatrixResponse calculateDistanceMatrix(DistanceMatrixRequest request) {
String url = buildRequestUrl(request);
logger.info("Making request to: {}", url);
HttpGet httpGet = new HttpGet(url);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
String responseBody = EntityUtils.toString(response.getEntity());
if (response.getCode() != 200) {
throw new RuntimeException("HTTP error: " + response.getCode() + " - " + responseBody);
}
DistanceMatrixResponse matrixResponse = objectMapper.readValue(responseBody, DistanceMatrixResponse.class);
// Check API status
if (!"OK".equals(matrixResponse.getStatus())) {
throw new RuntimeException("API Error: " + matrixResponse.getStatus() +
" - " + matrixResponse.getErrorMessage());
}
return matrixResponse;
} catch (IOException e) {
throw new RuntimeException("Failed to execute Distance Matrix request", e);
}
}
private String buildRequestUrl(DistanceMatrixRequest request) {
return config.getBaseUrl() + "?" + request.toQueryString() + "&key=" + config.getApiKey();
}
public void close() throws IOException {
httpClient.close();
}
}
5. Usage Example
DistanceMatrixDemo.java - Demonstration class
import java.util.Arrays;
import java.util.List;
public class DistanceMatrixDemo {
public static void main(String[] args) {
// Replace with your actual API key
String apiKey = "YOUR_API_KEY_HERE";
ApiConfig config = new ApiConfig(apiKey);
DistanceMatrixService service = new DistanceMatrixService(config);
try {
// Example 1: Simple driving distance between two points
DistanceMatrixRequest request1 = new DistanceMatrixRequest.Builder()
.addOrigin("New York, NY")
.addOrigin("Boston, MA")
.addDestination("Washington, DC")
.addDestination("Philadelphia, PA")
.withMode(TravelMode.DRIVING)
.withUnits("imperial")
.build();
System.out.println("=== Example 1: Multiple Origins to Multiple Destinations ===");
DistanceMatrixResponse response1 = service.calculateDistanceMatrix(request1);
printResponse(response1);
// Example 2: Transit with specific modes
DistanceMatrixRequest request2 = new DistanceMatrixRequest.Builder()
.addOrigin("Times Square, New York, NY")
.addDestination("Central Park, New York, NY")
.withMode(TravelMode.TRANSIT)
.withTransitModes(Arrays.asList(TransitMode.SUBWAY, TransitMode.BUS))
.build();
System.out.println("\n=== Example 2: Transit Directions ===");
DistanceMatrixResponse response2 = service.calculateDistanceMatrix(request2);
printResponse(response2);
// Example 3: Walking distance
DistanceMatrixRequest request3 = new DistanceMatrixRequest.Builder()
.addOrigin("40.748817,-73.985428") // Coordinates (Empire State Building)
.addDestination("40.758896,-73.987319") // Coordinates (Radio City Music Hall)
.withMode(TravelMode.WALKING)
.withUnits("metric")
.build();
System.out.println("\n=== Example 3: Walking Directions with Coordinates ===");
DistanceMatrixResponse response3 = service.calculateDistanceMatrix(request3);
printResponse(response3);
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
} finally {
try {
service.close();
} catch (Exception e) {
System.err.println("Error closing service: " + e.getMessage());
}
}
}
private static void printResponse(DistanceMatrixResponse response) {
List<String> origins = response.getOriginAddresses();
List<String> destinations = response.getDestinationAddresses();
for (int i = 0; i < origins.size(); i++) {
System.out.println("\nFrom: " + origins.get(i));
Row row = response.getRows().get(i);
for (int j = 0; j < destinations.size(); j++) {
Element element = row.getElements().get(j);
System.out.println(" To: " + destinations.get(j));
if ("OK".equals(element.getStatus())) {
System.out.println(" Distance: " + element.getDistance().getText());
System.out.println(" Duration: " + element.getDuration().getText());
if (element.getDurationInTraffic() != null) {
System.out.println(" Duration in Traffic: " +
element.getDurationInTraffic().getText());
}
} else {
System.out.println(" Status: " + element.getStatus());
}
}
}
}
}
Expected Output
=== Example 1: Multiple Origins to Multiple Destinations === From: New York, NY, USA To: Washington, DC, USA Distance: 225 mi Duration: 3 hours 52 mins To: Philadelphia, PA, USA Distance: 94.6 mi Duration: 1 hour 44 mins From: Boston, MA, USA To: Washington, DC, USA Distance: 439 mi Duration: 7 hours 18 mins To: Philadelphia, PA, USA Distance: 305 mi Duration: 5 hours 15 mins
Best Practices and Considerations
- API Key Security: Never hardcode API keys. Use environment variables or secure configuration services.
- Rate Limiting: Implement retry logic with exponential backoff to handle rate limits.
- Caching: Cache frequent distance calculations to reduce API calls and costs.
- Error Handling: Always check the
statusfield in the response for "OK", "ZERO_RESULTS", "NOT_FOUND", etc. - Input Validation: Validate and sanitize addresses before making API calls.
- Cost Optimization: The Distance Matrix API has costs based on the number of elements (origins × destinations).
This implementation provides a robust, type-safe way to interact with the Google Maps Distance Matrix API, making it easy to integrate distance calculations into your Java applications.
Java Observability, Logging Intelligence & AI-Driven Monitoring (APM, Tracing, Logs & Anomaly Detection)
https://macronepal.com/blog/beyond-metrics-observing-serverless-and-traditional-java-applications-with-thundra-apm/
Explains using Thundra APM to observe both serverless and traditional Java applications by combining tracing, metrics, and logs into a unified observability platform for faster debugging and performance insights.
https://macronepal.com/blog/dynatrace-oneagent-in-java-2/
Explains Dynatrace OneAgent for Java, which automatically instruments JVM applications to capture metrics, traces, and logs, enabling full-stack monitoring and root-cause analysis with minimal configuration.
https://macronepal.com/blog/lightstep-java-sdk-distributed-tracing-and-observability-implementation/
Explains Lightstep Java SDK for distributed tracing, helping developers track requests across microservices and identify latency issues using OpenTelemetry-based observability.
https://macronepal.com/blog/honeycomb-io-beeline-for-java-complete-guide-2/
Explains Honeycomb Beeline for Java, which provides high-cardinality observability and deep query capabilities to understand complex system behavior and debug distributed systems efficiently.
https://macronepal.com/blog/lumigo-for-serverless-in-java-complete-distributed-tracing-guide-2/
Explains Lumigo for Java serverless applications, offering automatic distributed tracing, log correlation, and error tracking to simplify debugging in cloud-native environments. (Lumigo Docs)
https://macronepal.com/blog/from-noise-to-signals-implementing-log-anomaly-detection-in-java-applications/
Explains how to detect anomalies in Java logs using behavioral patterns and machine learning techniques to separate meaningful incidents from noisy log data and improve incident response.
https://macronepal.com/blog/ai-powered-log-analysis-in-java-from-reactive-debugging-to-proactive-insights/
Explains AI-driven log analysis for Java applications, shifting from manual debugging to predictive insights that identify issues early and improve system reliability using intelligent log processing.
https://macronepal.com/blog/titliel-java-logging-best-practices/
Explains best practices for Java logging, focusing on structured logs, proper log levels, performance optimization, and ensuring logs are useful for debugging and observability systems.
https://macronepal.com/blog/seeking-a-loguru-for-java-the-quest-for-elegant-and-simple-logging/
Explains the search for simpler, more elegant logging frameworks in Java, comparing modern logging approaches that aim to reduce complexity while improving readability and developer experience.