Mapbox Directions API provides powerful routing capabilities for driving, walking, cycling, and multimodal transportation. Java applications can leverage these APIs to build navigation systems, logistics optimization, delivery route planning, and location-based services. Let's explore how to implement Mapbox Directions API integrations in Java.
Why Mapbox Directions API for Java?
Key integration scenarios:
- Delivery Route Optimization - Calculate optimal routes for delivery drivers
- Navigation Applications - Build turn-by-turn navigation systems
- Logistics Planning - Optimize fleet routes and schedules
- Multi-stop Routing - Plan efficient routes with multiple waypoints
- Real-time Traffic - Incorporate live traffic conditions
Mapbox Configuration and Setup
1. Mapbox Configuration
@Configuration
@ConfigurationProperties(prefix = "mapbox")
@Data
public class MapboxConfig {
private String accessToken;
private String baseUrl = "https://api.mapbox.com";
private int timeoutSeconds = 30;
private int maxWaypoints = 25; // Mapbox limit for directions API
public String getDirectionsUrl() {
return baseUrl + "/directions/v5/mapbox";
}
public String getMatrixUrl() {
return baseUrl + "/directions-matrix/v1/mapbox";
}
public String getOptimizationUrl() {
return baseUrl + "/optimized-trips/v1/mapbox";
}
public String getIsochronesUrl() {
return baseUrl + "/isochrone/v1/mapbox";
}
}
2. Core Mapbox Client
@Service
@Slf4j
public class MapboxApiClient {
private final MapboxConfig config;
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
public MapboxApiClient(MapboxConfig config) {
this.config = config;
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.httpClient = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor(log::info).setLevel(HttpLoggingInterceptor.Level.BASIC))
.connectTimeout(config.getTimeoutSeconds(), TimeUnit.SECONDS)
.readTimeout(config.getTimeoutSeconds(), TimeUnit.SECONDS)
.build();
}
// Generic GET request for Mapbox APIs
protected <T> T executeGet(String endpoint, Map<String, String> params, Class<T> responseType)
throws MapboxApiException {
try {
HttpUrl.Builder urlBuilder = HttpUrl.parse(endpoint).newBuilder();
// Add access token
urlBuilder.addQueryParameter("access_token", config.getAccessToken());
// Add other parameters
if (params != null) {
params.forEach(urlBuilder::addQueryParameter);
}
Request request = new Request.Builder()
.url(urlBuilder.build())
.header("Accept", "application/json")
.header("User-Agent", "Java-Mapbox-Client/1.0")
.get()
.build();
Response response = httpClient.newCall(request).execute();
if (!response.isSuccessful()) {
handleErrorResponse(response, endpoint);
}
String responseBody = response.body().string();
return objectMapper.readValue(responseBody, responseType);
} catch (Exception e) {
throw new MapboxApiException("Mapbox API request failed: " + endpoint, e);
}
}
private void handleErrorResponse(Response response, String endpoint) throws IOException, MapboxApiException {
int code = response.code();
String body = response.body() != null ? response.body().string() : "No response body";
log.error("Mapbox API error - Status: {}, Endpoint: {}, Body: {}", code, endpoint, body);
try {
MapboxError error = objectMapper.readValue(body, MapboxError.class);
throw new MapboxApiException("Mapbox API Error: " + error.getMessage(), code);
} catch (Exception e) {
throw new MapboxApiException("Mapbox API error - Status: " + code + ", Endpoint: " + endpoint, code);
}
}
}
Directions Service
1. Core Directions Service
@Service
@Slf4j
public class MapboxDirectionsService extends MapboxApiClient {
public MapboxDirectionsService(MapboxConfig config) {
super(config);
}
public DirectionsResponse getDirections(DirectionsRequest request) throws MapboxApiException {
validateDirectionsRequest(request);
Map<String, String> params = buildDirectionsParams(request);
String coordinates = buildCoordinatesString(request.getWaypoints());
String endpoint = String.format("%s/%s/%s",
config.getDirectionsUrl(),
request.getProfile().getValue(),
coordinates);
return executeGet(endpoint, params, DirectionsResponse.class);
}
public Route getSimpleRoute(Location origin, Location destination, RoutingProfile profile)
throws MapboxApiException {
DirectionsRequest request = DirectionsRequest.builder()
.waypoints(Arrays.asList(origin, destination))
.profile(profile)
.build();
DirectionsResponse response = getDirections(request);
return response.getRoutes().get(0);
}
public List<Route> getRoutesWithAlternatives(Location origin, Location destination,
RoutingProfile profile, int alternatives)
throws MapboxApiException {
DirectionsRequest request = DirectionsRequest.builder()
.waypoints(Arrays.asList(origin, destination))
.profile(profile)
.alternatives(alternatives)
.build();
DirectionsResponse response = getDirections(request);
return response.getRoutes();
}
public Route getFastestRoute(Location origin, Location destination, RoutingProfile profile)
throws MapboxApiException {
List<Route> routes = getRoutesWithAlternatives(origin, destination, profile, 3);
return routes.stream()
.min(Comparator.comparing(Route::getDuration))
.orElseThrow(() -> new MapboxApiException("No routes found"));
}
public Route getShortestRoute(Location origin, Location destination, RoutingProfile profile)
throws MapboxApiException {
List<Route> routes = getRoutesWithAlternatives(origin, destination, profile, 3);
return routes.stream()
.min(Comparator.comparing(Route::getDistance))
.orElseThrow(() -> new MapboxApiException("No routes found"));
}
public Route getRouteWithWaypoints(List<Location> waypoints, RoutingProfile profile)
throws MapboxApiException {
if (waypoints.size() < 2) {
throw new MapboxApiException("At least 2 waypoints required");
}
if (waypoints.size() > config.getMaxWaypoints()) {
throw new MapboxApiException("Too many waypoints. Maximum: " + config.getMaxWaypoints());
}
DirectionsRequest request = DirectionsRequest.builder()
.waypoints(waypoints)
.profile(profile)
.build();
DirectionsResponse response = getDirections(request);
return response.getRoutes().get(0);
}
public Route getRouteWithOptions(Location origin, Location destination,
RoutingProfile profile, RouteOptions options)
throws MapboxApiException {
DirectionsRequest request = DirectionsRequest.builder()
.waypoints(Arrays.asList(origin, destination))
.profile(profile)
.alternatives(options.getAlternatives())
.annotations(options.getAnnotations())
.geometries(options.getGeometries())
.overview(options.getOverview())
.steps(options.isSteps())
.language(options.getLanguage())
.build();
DirectionsResponse response = getDirections(request);
return response.getRoutes().get(0);
}
// Helper methods
private void validateDirectionsRequest(DirectionsRequest request) throws MapboxApiException {
if (request.getWaypoints() == null || request.getWaypoints().size() < 2) {
throw new MapboxApiException("At least 2 waypoints required");
}
if (request.getWaypoints().size() > config.getMaxWaypoints()) {
throw new MapboxApiException("Too many waypoints. Maximum: " + config.getMaxWaypoints());
}
if (request.getProfile() == null) {
throw new MapboxApiException("Routing profile is required");
}
}
private Map<String, String> buildDirectionsParams(DirectionsRequest request) {
Map<String, String> params = new HashMap<>();
if (request.getAlternatives() != null) {
params.put("alternatives", String.valueOf(request.getAlternatives()));
}
if (request.getAnnotations() != null && !request.getAnnotations().isEmpty()) {
params.put("annotations", String.join(",", request.getAnnotations()));
}
if (request.getGeometries() != null) {
params.put("geometries", request.getGeometries().getValue());
}
if (request.getOverview() != null) {
params.put("overview", request.getOverview().getValue());
}
if (request.isSteps() != null) {
params.put("steps", String.valueOf(request.isSteps()));
}
if (request.getLanguage() != null) {
params.put("language", request.getLanguage());
}
if (request.getBearings() != null && !request.getBearings().isEmpty()) {
params.put("bearings", buildBearingsString(request.getBearings()));
}
if (request.getRadiuses() != null && !request.getRadiuses().isEmpty()) {
params.put("radiuses", buildRadiusesString(request.getRadiuses()));
}
if (request.getApproaches() != null && !request.getApproaches().isEmpty()) {
params.put("approaches", buildApproachesString(request.getApproaches()));
}
return params;
}
private String buildCoordinatesString(List<Location> waypoints) {
return waypoints.stream()
.map(location -> String.format(Locale.US, "%.6f,%.6f", location.getLongitude(), location.getLatitude()))
.collect(Collectors.joining(";"));
}
private String buildBearingsString(List<Bearing> bearings) {
return bearings.stream()
.map(bearing -> String.format("%d,%d", bearing.getAngle(), bearing.getDeviation()))
.collect(Collectors.joining(";"));
}
private String buildRadiusesString(List<Integer> radiuses) {
return radiuses.stream()
.map(String::valueOf)
.collect(Collectors.joining(";"));
}
private String buildApproachesString(List<String> approaches) {
return String.join(";", approaches);
}
}
Matrix Service for Multiple Origins/Destinations
1. Distance Matrix Service
@Service
@Slf4j
public class MapboxMatrixService extends MapboxApiClient {
public MapboxMatrixService(MapboxConfig config) {
super(config);
}
public MatrixResponse getDistanceMatrix(List<Location> sources, List<Location> destinations,
RoutingProfile profile) throws MapboxApiException {
validateMatrixRequest(sources, destinations);
Map<String, String> params = new HashMap<>();
params.put("sources", buildIndicesString(sources.size()));
params.put("destinations", buildIndicesString(destinations.size()));
String coordinates = buildCoordinatesString(sources, destinations);
String endpoint = String.format("%s/%s/%s",
config.getMatrixUrl(),
profile.getValue(),
coordinates);
return executeGet(endpoint, params, MatrixResponse.class);
}
public DistanceMatrix buildDistanceMatrix(List<Location> locations, RoutingProfile profile)
throws MapboxApiException {
MatrixResponse response = getDistanceMatrix(locations, locations, profile);
return new DistanceMatrix(locations, response.getDurations(), response.getDistances());
}
public Location findNearestLocation(Location target, List<Location> candidates, RoutingProfile profile)
throws MapboxApiException {
MatrixResponse response = getDistanceMatrix(Arrays.asList(target), candidates, profile);
if (response.getDurations() == null || response.getDurations().length == 0) {
throw new MapboxApiException("No durations returned from matrix API");
}
double[] durations = response.getDurations()[0]; // First row (from target to all candidates)
int minIndex = findMinIndex(durations);
return candidates.get(minIndex);
}
public List<Location> findLocationsWithinTime(Location origin, List<Location> candidates,
int maxTimeSeconds, RoutingProfile profile)
throws MapboxApiException {
MatrixResponse response = getDistanceMatrix(Arrays.asList(origin), candidates, profile);
if (response.getDurations() == null || response.getDurations().length == 0) {
return Collections.emptyList();
}
double[] durations = response.getDurations()[0];
List<Location> withinRange = new ArrayList<>();
for (int i = 0; i < durations.length; i++) {
if (durations[i] <= maxTimeSeconds) {
withinRange.add(candidates.get(i));
}
}
return withinRange;
}
public OptimizedRoute planDeliveryRoute(List<Location> stops, Location depot, RoutingProfile profile)
throws MapboxApiException {
// Use optimization API for better results with many stops
List<Location> allStops = new ArrayList<>();
allStops.add(depot);
allStops.addAll(stops);
allStops.add(depot); // Return to depot
MatrixResponse matrix = getDistanceMatrix(allStops, allStops, profile);
return solveTSP(allStops, matrix);
}
// Helper methods
private void validateMatrixRequest(List<Location> sources, List<Location> destinations)
throws MapboxApiException {
if (sources == null || sources.isEmpty()) {
throw new MapboxApiException("At least one source required");
}
if (destinations == null || destinations.isEmpty()) {
throw new MapboxApiException("At least one destination required");
}
if (sources.size() * destinations.size() > 625) { // Mapbox matrix limit
throw new MapboxApiException("Matrix too large. Maximum 25x25 for Mapbox Matrix API");
}
}
private String buildCoordinatesString(List<Location> sources, List<Location> destinations) {
List<Location> allLocations = new ArrayList<>();
allLocations.addAll(sources);
allLocations.addAll(destinations);
return allLocations.stream()
.map(location -> String.format(Locale.US, "%.6f,%.6f", location.getLongitude(), location.getLatitude()))
.collect(Collectors.joining(";"));
}
private String buildIndicesString(int count) {
return IntStream.range(0, count)
.mapToObj(String::valueOf)
.collect(Collectors.joining(";"));
}
private int findMinIndex(double[] array) {
int minIndex = 0;
for (int i = 1; i < array.length; i++) {
if (array[i] < array[minIndex]) {
minIndex = i;
}
}
return minIndex;
}
private OptimizedRoute solveTSP(List<Location> locations, MatrixResponse matrix) {
// Simplified TSP solver - in production, use a proper optimization library
// This is a naive nearest neighbor implementation
List<Integer> route = new ArrayList<>();
boolean[] visited = new boolean[locations.size()];
route.add(0); // Start at depot
visited[0] = true;
int current = 0;
for (int i = 1; i < locations.size(); i++) {
int next = findNearestUnvisited(current, visited, matrix.getDurations());
route.add(next);
visited[next] = true;
current = next;
}
return new OptimizedRoute(locations, route, matrix);
}
private int findNearestUnvisited(int current, boolean[] visited, double[][] durations) {
int nearest = -1;
double minDuration = Double.MAX_VALUE;
for (int i = 0; i < visited.length; i++) {
if (!visited[i] && durations[current][i] < minDuration) {
minDuration = durations[current][i];
nearest = i;
}
}
return nearest;
}
}
Optimization Service
1. Route Optimization Service
@Service
@Slf4j
public class MapboxOptimizationService extends MapboxApiClient {
public MapboxOptimizationService(MapboxConfig config) {
super(config);
}
public OptimizationResponse getOptimizedRoute(List<Location> locations, RoutingProfile profile)
throws MapboxApiException {
return getOptimizedRoute(locations, profile, new OptimizationOptions());
}
public OptimizationResponse getOptimizedRoute(List<Location> locations, RoutingProfile profile,
OptimizationOptions options) throws MapboxApiException {
validateOptimizationRequest(locations);
Map<String, String> params = buildOptimizationParams(options);
String coordinates = buildCoordinatesString(locations);
String endpoint = String.format("%s/%s/%s",
config.getOptimizationUrl(),
profile.getValue(),
coordinates);
return executeGet(endpoint, params, OptimizationResponse.class);
}
public OptimizedTrip planDeliveryTrip(List<DeliveryStop> stops, Location depot,
RoutingProfile profile) throws MapboxApiException {
List<Location> allLocations = new ArrayList<>();
allLocations.add(depot);
for (DeliveryStop stop : stops) {
allLocations.add(stop.getLocation());
}
OptimizationOptions options = new OptimizationOptions();
options.setSource("first");
options.setDestination("last");
options.setRoundTrip(true);
OptimizationResponse response = getOptimizedRoute(allLocations, profile, options);
return mapToOptimizedTrip(response, stops, depot);
}
public List<OptimizedTrip> planMultipleVehicleRoutes(List<Vehicle> vehicles, List<DeliveryStop> stops,
RoutingProfile profile) throws MapboxApiException {
// This would use Mapbox Optimization API with vehicle constraints
// For simplicity, we're doing a basic implementation
List<OptimizedTrip> trips = new ArrayList<>();
for (Vehicle vehicle : vehicles) {
List<DeliveryStop> vehicleStops = assignStopsToVehicle(stops, vehicle, vehicles.size());
OptimizedTrip trip = planDeliveryTrip(vehicleStops, vehicle.getDepot(), profile);
trips.add(trip);
}
return trips;
}
// Helper methods
private void validateOptimizationRequest(List<Location> locations) throws MapboxApiException {
if (locations == null || locations.size() < 2) {
throw new MapboxApiException("At least 2 locations required for optimization");
}
if (locations.size() > 12) { // Mapbox optimization limit for standard plan
throw new MapboxApiException("Too many locations for optimization. Maximum: 12");
}
}
private Map<String, String> buildOptimizationParams(OptimizationOptions options) {
Map<String, String> params = new HashMap<>();
if (options.getSource() != null) {
params.put("source", options.getSource());
}
if (options.getDestination() != null) {
params.put("destination", options.getDestination());
}
if (options.isRoundTrip() != null) {
params.put("roundtrip", String.valueOf(options.isRoundTrip()));
}
if (options.getRadiuses() != null) {
params.put("radiuses", options.getRadiuses());
}
return params;
}
private List<DeliveryStop> assignStopsToVehicle(List<DeliveryStop> stops, Vehicle vehicle, int totalVehicles) {
// Simple round-robin assignment
List<DeliveryStop> assigned = new ArrayList<>();
int vehicleIndex = vehicles.indexOf(vehicle);
for (int i = vehicleIndex; i < stops.size(); i += totalVehicles) {
assigned.add(stops.get(i));
}
return assigned;
}
private OptimizedTrip mapToOptimizedTrip(OptimizationResponse response, List<DeliveryStop> stops, Location depot) {
OptimizedTrip trip = new OptimizedTrip();
trip.setDepot(depot);
trip.setStops(stops);
trip.setRoute(response.getTrips().get(0));
trip.setTotalDistance(response.getTrips().get(0).getDistance());
trip.setTotalDuration(response.getTrips().get(0).getDuration());
return trip;
}
}
Data Models
1. Core Mapbox Data Models
@Data
@Builder
public class DirectionsRequest {
private List<Location> waypoints;
private RoutingProfile profile;
private Integer alternatives;
private List<String> annotations; // duration,distance,speed,congestion
private GeometryType geometries;
private OverviewType overview;
private Boolean steps;
private String language;
private List<Bearing> bearings;
private List<Integer> radiuses;
private List<String> approaches; // curb, unrestricted
@Data
@Builder
public static class Bearing {
private Integer angle;
private Integer deviation;
}
}
@Data
public class DirectionsResponse {
private List<Route> routes;
private List<Waypoint> waypoints;
private String code;
private String uuid;
@Data
public static class Route {
private Double distance; // meters
private Double duration; // seconds
private String geometry; // polyline encoded
private List<Leg> legs;
private String weightName;
private Double weight;
@Data
public static class Leg {
private Double distance;
private Double duration;
private String summary;
private List<Step> steps;
@Data
public static class Step {
private String name;
private Double distance;
private Double duration;
private String geometry;
private String maneuver;
private List<Intersection> intersections;
}
}
}
}
@Data
public class MatrixResponse {
private List<List<Double>> durations; // seconds
private List<List<Double>> distances; // meters
private List<Location> sources;
private List<Location> destinations;
private String code;
}
@Data
public class OptimizationResponse {
private List<Route> trips;
private List<Waypoint> waypoints;
private String code;
}
@Data
public class Location {
private Double latitude;
private Double longitude;
private String name;
private String address;
public Location() {}
public Location(Double latitude, Double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
public Location(Double latitude, Double longitude, String name) {
this(latitude, longitude);
this.name = name;
}
}
@Data
public class RouteOptions {
private Integer alternatives = 0;
private List<String> annotations = Arrays.asList("duration", "distance");
private GeometryType geometries = GeometryType.POLYLINE6;
private OverviewType overview = OverviewType.SIMPLIFIED;
private Boolean steps = false;
private String language = "en";
}
@Data
public class OptimizationOptions {
private String source = "first";
private String destination = "last";
private Boolean roundTrip = true;
private String radiuses;
}
// Enums
public enum RoutingProfile {
DRIVING("driving"),
DRIVING_TRAFFIC("driving-traffic"),
WALKING("walking"),
CYCLING("cycling");
private final String value;
RoutingProfile(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public enum GeometryType {
GEOJSON("geojson"),
POLYLINE("polyline"),
POLYLINE6("polyline6");
private final String value;
GeometryType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public enum OverviewType {
FULL("full"),
SIMPLIFIED("simplified"),
FALSE("false");
private final String value;
OverviewType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
// Business domain models
@Data
public class DeliveryStop {
private String id;
private Location location;
private String address;
private String customerName;
private Integer deliveryWindowStart; // minutes from midnight
private Integer deliveryWindowEnd;
private Integer serviceTime; // seconds
private Integer priority;
}
@Data
public class Vehicle {
private String id;
private Location depot;
private Integer capacity;
private Integer maxRouteDuration; // seconds
private Integer maxStops;
private RoutingProfile profile;
}
@Data
public class OptimizedRoute {
private List<Location> locations;
private List<Integer> visitOrder;
private Double totalDistance;
private Double totalDuration;
private MatrixResponse matrix;
public OptimizedRoute(List<Location> locations, List<Integer> visitOrder, MatrixResponse matrix) {
this.locations = locations;
this.visitOrder = visitOrder;
this.matrix = matrix;
calculateTotals();
}
private void calculateTotals() {
this.totalDistance = 0.0;
this.totalDuration = 0.0;
for (int i = 0; i < visitOrder.size() - 1; i++) {
int from = visitOrder.get(i);
int to = visitOrder.get(i + 1);
totalDistance += matrix.getDistances().get(from).get(to);
totalDuration += matrix.getDurations().get(from).get(to);
}
}
public List<Location> getOrderedLocations() {
return visitOrder.stream()
.map(locations::get)
.collect(Collectors.toList());
}
}
@Data
public class DistanceMatrix {
private List<Location> locations;
private List<List<Double>> durations;
private List<List<Double>> distances;
public DistanceMatrix(List<Location> locations, List<List<Double>> durations, List<List<Double>> distances) {
this.locations = locations;
this.durations = durations;
this.distances = distances;
}
public Double getDuration(Location from, Location to) {
int fromIndex = locations.indexOf(from);
int toIndex = locations.indexOf(to);
return durations.get(fromIndex).get(toIndex);
}
public Double getDistance(Location from, Location to) {
int fromIndex = locations.indexOf(from);
int toIndex = locations.indexOf(to);
return distances.get(fromIndex).get(toIndex);
}
}
REST Controller
1. Mapbox Directions API
@RestController
@RequestMapping("/api/mapbox")
@Slf4j
public class MapboxController {
private final MapboxDirectionsService directionsService;
private final MapboxMatrixService matrixService;
private final MapboxOptimizationService optimizationService;
public MapboxController(MapboxDirectionsService directionsService,
MapboxMatrixService matrixService,
MapboxOptimizationService optimizationService) {
this.directionsService = directionsService;
this.matrixService = matrixService;
this.optimizationService = optimizationService;
}
@PostMapping("/directions")
public ResponseEntity<DirectionsResponse> getDirections(@RequestBody DirectionsRequest request) {
try {
DirectionsResponse response = directionsService.getDirections(request);
return ResponseEntity.ok(response);
} catch (MapboxApiException e) {
log.error("Failed to get directions", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@GetMapping("/directions/simple")
public ResponseEntity<Route> getSimpleRoute(
@RequestParam Double originLat, @RequestParam Double originLng,
@RequestParam Double destLat, @RequestParam Double destLng,
@RequestParam(defaultValue = "driving") String profile) {
try {
Location origin = new Location(originLat, originLng);
Location destination = new Location(destLat, destLng);
RoutingProfile routingProfile = RoutingProfile.valueOf(profile.toUpperCase());
Route route = directionsService.getSimpleRoute(origin, destination, routingProfile);
return ResponseEntity.ok(route);
} catch (MapboxApiException e) {
log.error("Failed to get simple route", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@PostMapping("/matrix")
public ResponseEntity<MatrixResponse> getDistanceMatrix(@RequestBody MatrixRequest request) {
try {
MatrixResponse response = matrixService.getDistanceMatrix(
request.getSources(),
request.getDestinations(),
request.getProfile()
);
return ResponseEntity.ok(response);
} catch (MapboxApiException e) {
log.error("Failed to get distance matrix", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@PostMapping("/nearest")
public ResponseEntity<Location> findNearestLocation(@RequestBody NearestLocationRequest request) {
try {
Location nearest = matrixService.findNearestLocation(
request.getTarget(),
request.getCandidates(),
request.getProfile()
);
return ResponseEntity.ok(nearest);
} catch (MapboxApiException e) {
log.error("Failed to find nearest location", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@PostMapping("/optimization/delivery")
public ResponseEntity<OptimizedTrip> planDeliveryRoute(@RequestBody DeliveryPlanRequest request) {
try {
OptimizedTrip trip = optimizationService.planDeliveryTrip(
request.getStops(),
request.getDepot(),
request.getProfile()
);
return ResponseEntity.ok(trip);
} catch (MapboxApiException e) {
log.error("Failed to plan delivery route", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@PostMapping("/optimization/fleet")
public ResponseEntity<List<OptimizedTrip>> planFleetRoutes(@RequestBody FleetPlanRequest request) {
try {
List<OptimizedTrip> trips = optimizationService.planMultipleVehicleRoutes(
request.getVehicles(),
request.getStops(),
request.getProfile()
);
return ResponseEntity.ok(trips);
} catch (MapboxApiException e) {
log.error("Failed to plan fleet routes", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@GetMapping("/routes/alternatives")
public ResponseEntity<List<Route>> getRouteAlternatives(
@RequestParam Double originLat, @RequestParam Double originLng,
@RequestParam Double destLat, @RequestParam Double destLng,
@RequestParam(defaultValue = "driving") String profile,
@RequestParam(defaultValue = "3") Integer alternatives) {
try {
Location origin = new Location(originLat, originLng);
Location destination = new Location(destLat, destLng);
RoutingProfile routingProfile = RoutingProfile.valueOf(profile.toUpperCase());
List<Route> routes = directionsService.getRoutesWithAlternatives(
origin, destination, routingProfile, alternatives);
return ResponseEntity.ok(routes);
} catch (MapboxApiException e) {
log.error("Failed to get route alternatives", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
// Request DTOs
@Data
public static class MatrixRequest {
private List<Location> sources;
private List<Location> destinations;
private RoutingProfile profile;
}
@Data
public static class NearestLocationRequest {
private Location target;
private List<Location> candidates;
private RoutingProfile profile;
}
@Data
public static class DeliveryPlanRequest {
private List<DeliveryStop> stops;
private Location depot;
private RoutingProfile profile;
}
@Data
public static class FleetPlanRequest {
private List<Vehicle> vehicles;
private List<DeliveryStop> stops;
private RoutingProfile profile;
}
}
Error Handling
1. Custom Exceptions
public class MapboxApiException extends Exception {
private final Integer statusCode;
public MapboxApiException(String message) {
super(message);
this.statusCode = null;
}
public MapboxApiException(String message, Integer statusCode) {
super(message);
this.statusCode = statusCode;
}
public MapboxApiException(String message, Throwable cause) {
super(message, cause);
this.statusCode = null;
}
public Integer getStatusCode() {
return statusCode;
}
}
@Data
public class MapboxError {
private String message;
private String code;
}
@ControllerAdvice
public class MapboxExceptionHandler {
@ExceptionHandler(MapboxApiException.class)
public ResponseEntity<ErrorResponse> handleMapboxApiException(MapboxApiException e) {
log.error("Mapbox API error", e);
ErrorResponse error = new ErrorResponse(
"MAPBOX_API_ERROR",
e.getMessage(),
e.getStatusCode(),
LocalDateTime.now()
);
return ResponseEntity.status(
e.getStatusCode() != null ? HttpStatus.valueOf(e.getStatusCode()) : HttpStatus.INTERNAL_SERVER_ERROR
).body(error);
}
@Data
public static class ErrorResponse {
private final String error;
private final String message;
private final Integer statusCode;
private final LocalDateTime timestamp;
}
}
Configuration
1. Application Properties
# application.yml
mapbox:
access-token: ${MAPBOX_ACCESS_TOKEN}
base-url: https://api.mapbox.com
timeout-seconds: 30
max-waypoints: 25
Best Practices
- Rate Limiting - Implement proper rate limiting for Mapbox API calls
- Caching - Cache frequent routes and matrix calculations
- Error Handling - Handle Mapbox-specific error codes and rate limits
- Coordinate Validation - Validate latitude/longitude ranges
- Batch Processing - Use matrix API for multiple calculations
- Cost Optimization - Minimize API calls through caching and batching
- Monitoring - Track API usage and performance metrics
Conclusion
Integrating Mapbox Directions API with Java applications enables powerful routing and navigation capabilities. By implementing the patterns shown here, Java developers can:
- Build Navigation Systems - Create turn-by-turn navigation applications
- Optimize Delivery Routes - Plan efficient routes for delivery fleets
- Calculate Travel Times - Provide accurate ETAs and distance calculations
- Solve Logistics Problems - Optimize vehicle routing and scheduling
- Enhance Location Services - Add sophisticated routing to location-based applications
The combination of robust API clients, comprehensive service implementations, and proper error handling creates an enterprise-ready Mapbox integration that can scale to handle complex routing requirements.
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.