# jsprit
jsprit is a Java-based, open-source toolkit for solving rich Vehicle Routing Problems (VRP) and Traveling Salesman Problems (TSP). It provides a flexible, lightweight framework based on a powerful meta-heuristic algorithm that combines ruin-and-recreate principles with adaptive large neighborhood search. The library supports a wide range of problem variants including Capacitated VRP, Multiple Depot VRP, VRP with Time Windows, VRP with Pickups and Deliveries, Heterogeneous Fleet VRP, and various combinations thereof.
The toolkit is designed with modularity and extensibility in mind, allowing users to define custom constraints, objective functions, and algorithm configurations. jsprit uses builder patterns throughout its API to construct immutable problem definitions, making it easy to set up complex routing scenarios. The library includes visualization tools for analyzing problems and solutions, and supports both finite and infinite fleet sizes for tactical and operational planning use cases.
## Getting Started with Maven
Add jsprit to your project using Maven to access all core vehicle routing capabilities.
```xml
com.graphhopper
jsprit-core
1.7.3
com.graphhopper
jsprit-analysis
1.7.3
```
## VehicleTypeImpl.Builder - Define Vehicle Types
Create vehicle types with capacity dimensions, fixed costs, and variable costs per distance/time using the builder pattern.
```java
// Create a vehicle type with capacity of 200 units and cost parameters
VehicleTypeImpl vehicleType = VehicleTypeImpl.Builder.newInstance("largeVan")
.addCapacityDimension(0, 200) // Index 0 = weight capacity
.addCapacityDimension(1, 50) // Index 1 = volume capacity
.setFixedCost(100.0) // Fixed cost per vehicle used
.setCostPerDistance(1.0) // Cost per unit distance
.setCostPerTransportTime(0.5) // Cost per unit time
.setCostPerServiceTime(0.3) // Cost per service duration
.build();
// Create a smaller vehicle type for comparison
VehicleTypeImpl smallVan = VehicleTypeImpl.Builder.newInstance("smallVan")
.addCapacityDimension(0, 80)
.setFixedCost(50.0)
.setCostPerDistance(0.8)
.build();
```
## VehicleImpl.Builder - Create Vehicles
Build vehicles with start/end locations, time windows, skills, and breaks using the fluent builder API.
```java
// Create a vehicle starting at depot (10,10) with operating time window
VehicleImpl vehicle = VehicleImpl.Builder.newInstance("vehicle1")
.setStartLocation(Location.newInstance(10, 10))
.setEndLocation(Location.newInstance(10, 10)) // Return to depot
.setType(vehicleType)
.setEarliestStart(0) // Can depart at time 0
.setLatestArrival(480) // Must return by time 480 (e.g., 8 hours)
.setReturnToDepot(true)
.addSkill("refrigerated") // Vehicle has refrigeration capability
.addSkill("forklift")
.build();
// Vehicle that doesn't need to return to depot (open-ended route)
VehicleImpl openVehicle = VehicleImpl.Builder.newInstance("vehicle2")
.setStartLocation(Location.newInstance(20, 30))
.setType(smallVan)
.setReturnToDepot(false) // End location is endogenous
.build();
// Vehicle with scheduled break
Break lunch = Break.Builder.newInstance("lunch")
.setTimeWindow(TimeWindow.newInstance(180, 240)) // Break window
.setServiceTime(30) // 30-minute break
.build();
VehicleImpl vehicleWithBreak = VehicleImpl.Builder.newInstance("vehicle3")
.setStartLocation(Location.newInstance(15, 15))
.setType(vehicleType)
.setBreak(lunch)
.build();
```
## Service.Builder - Define Delivery/Pickup Services
Create service jobs representing single-location activities like deliveries, pickups, or service visits.
```java
// Basic delivery service at customer location
Service delivery1 = Service.Builder.newInstance("customer1")
.setLocation(Location.newInstance(25, 35))
.addSizeDimension(0, 20) // Delivers 20 units (weight)
.addSizeDimension(1, 5) // Delivers 5 units (volume)
.setServiceTime(15) // Takes 15 time units to service
.build();
// Service with time window constraint
Service delivery2 = Service.Builder.newInstance("customer2")
.setLocation(Location.newInstance(45, 68))
.addSizeDimension(0, 30)
.setServiceTime(20)
.setTimeWindow(TimeWindow.newInstance(60, 120)) // Must arrive 60-120
.build();
// Service with multiple time windows (customer available at different times)
Service delivery3 = Service.Builder.newInstance("customer3")
.setLocation(Location.newInstance(30, 50))
.addSizeDimension(0, 15)
.setServiceTime(10)
.addTimeWindow(TimeWindow.newInstance(0, 60)) // Morning slot
.addTimeWindow(TimeWindow.newInstance(180, 240)) // Afternoon slot
.build();
// Pickup job (negative demand = pickup)
Pickup pickup1 = Pickup.Builder.newInstance("pickup1")
.setLocation(Location.newInstance(55, 25))
.addSizeDimension(0, 25) // Picks up 25 units
.setServiceTime(10)
.addRequiredSkill("refrigerated") // Requires refrigerated vehicle
.build();
// Service with priority (1=highest, 10=lowest, default=2)
Service urgentDelivery = Service.Builder.newInstance("urgent")
.setLocation(Location.newInstance(40, 40))
.addSizeDimension(0, 10)
.setServiceTime(5)
.setPriority(1) // High priority
.build();
```
## Shipment.Builder - Define Pickup-Delivery Pairs
Create shipment jobs that require both pickup and delivery operations at different locations.
```java
// Basic shipment from pickup to delivery location
Shipment shipment1 = Shipment.Builder.newInstance("shipment1")
.setPickupLocation(Location.newInstance(5, 7))
.setDeliveryLocation(Location.newInstance(15, 25))
.addSizeDimension(0, 10) // Size of shipment
.setPickupServiceTime(5) // Time to load
.setDeliveryServiceTime(5) // Time to unload
.build();
// Shipment with time windows on both pickup and delivery
Shipment shipment2 = Shipment.Builder.newInstance("shipment2")
.setPickupLocation(Location.newInstance(10, 20))
.setDeliveryLocation(Location.newInstance(50, 60))
.addSizeDimension(0, 15)
.setPickupTimeWindow(TimeWindow.newInstance(0, 60)) // Pickup window
.setDeliveryTimeWindow(TimeWindow.newInstance(120, 180)) // Delivery window
.setPickupServiceTime(10)
.setDeliveryServiceTime(10)
.build();
// Shipment with max time in vehicle constraint (freshness/perishability)
Shipment perishable = Shipment.Builder.newInstance("perishable1")
.setPickupLocation(Location.newInstance(8, 12))
.setDeliveryLocation(Location.newInstance(45, 55))
.addSizeDimension(0, 20)
.setMaxTimeInVehicle(60) // Must be delivered within 60 units
.addRequiredSkill("refrigerated")
.build();
```
## VehicleRoutingProblem.Builder - Assemble the Problem
Combine vehicles and jobs into a complete vehicle routing problem definition.
```java
// Build a basic VRP with services
VehicleRoutingProblem.Builder vrpBuilder = VehicleRoutingProblem.Builder.newInstance();
// Add vehicles
vrpBuilder.addVehicle(vehicle);
vrpBuilder.addVehicle(openVehicle);
// Add jobs
vrpBuilder.addJob(delivery1);
vrpBuilder.addJob(delivery2);
vrpBuilder.addJob(delivery3);
vrpBuilder.addJob(pickup1);
vrpBuilder.addJob(shipment1);
vrpBuilder.addJob(shipment2);
// Set fleet size (INFINITE = can use unlimited copies of each vehicle type)
vrpBuilder.setFleetSize(VehicleRoutingProblem.FleetSize.INFINITE);
// Build the problem (uses Euclidean distances by default)
VehicleRoutingProblem problem = vrpBuilder.build();
// For finite fleet (use exact vehicles defined)
VehicleRoutingProblem finiteProblem = VehicleRoutingProblem.Builder.newInstance()
.addVehicle(vehicle)
.addJob(delivery1)
.addJob(delivery2)
.setFleetSize(VehicleRoutingProblem.FleetSize.FINITE)
.build();
```
## Jsprit - Create and Run Algorithm
Use the Jsprit factory to create an out-of-the-box algorithm and search for solutions.
```java
// Create algorithm with default settings
VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(problem);
// Search for solutions
Collection solutions = algorithm.searchSolutions();
// Get the best solution
VehicleRoutingProblemSolution bestSolution = Solutions.bestOf(solutions);
// Access solution details
System.out.println("Total cost: " + bestSolution.getCost());
System.out.println("Number of routes: " + bestSolution.getRoutes().size());
System.out.println("Unassigned jobs: " + bestSolution.getUnassignedJobs().size());
// Iterate through routes
for (VehicleRoute route : bestSolution.getRoutes()) {
System.out.println("\nVehicle: " + route.getVehicle().getId());
System.out.println("Start: " + route.getStart().getEndTime());
for (TourActivity activity : route.getActivities()) {
if (activity instanceof TourActivity.JobActivity) {
TourActivity.JobActivity jobAct = (TourActivity.JobActivity) activity;
System.out.println(" Job: " + jobAct.getJob().getId() +
" Arrive: " + activity.getArrTime() +
" End: " + activity.getEndTime());
}
}
System.out.println("End: " + route.getEnd().getArrTime());
}
```
## Jsprit.Builder - Customize Algorithm Parameters
Fine-tune the algorithm with custom parameters, iterations, threads, and strategies.
```java
// Build algorithm with custom parameters
VehicleRoutingAlgorithm customAlgorithm = Jsprit.Builder.newInstance(problem)
.setProperty(Jsprit.Parameter.ITERATIONS, "5000") // More iterations
.setProperty(Jsprit.Parameter.THREADS, "4") // Parallel execution
.setProperty(Jsprit.Parameter.FIXED_COST_PARAM, "0.5") // Consider fixed costs
.setProperty(Jsprit.Parameter.VEHICLE_SWITCH, "true") // Allow vehicle switching
.setProperty(Jsprit.Parameter.CONSTRUCTION, "regret_insertion")
.buildAlgorithm();
// Set maximum iterations programmatically
customAlgorithm.setMaxIterations(3000);
// Add termination criteria based on iterations without improvement
customAlgorithm.setPrematureAlgorithmTermination(
new IterationWithoutImprovementTermination(200)
);
// Add time-based termination (in seconds)
TimeTermination timeTermination = new TimeTermination(30.0);
customAlgorithm.setPrematureAlgorithmTermination(timeTermination);
customAlgorithm.addListener(timeTermination);
// Run the customized algorithm
Collection solutions = customAlgorithm.searchSolutions();
```
## Custom Transport Costs - Define Distance/Time Matrices
Implement custom transport costs using distance matrices or real-world routing data.
```java
// Create a custom cost matrix
VehicleRoutingTransportCostsMatrix.Builder costMatrixBuilder =
VehicleRoutingTransportCostsMatrix.Builder.newInstance(true); // true = symmetric
// Add distances between locations (by location ID)
costMatrixBuilder.addTransportDistance("depot", "customer1", 10.0);
costMatrixBuilder.addTransportDistance("depot", "customer2", 15.0);
costMatrixBuilder.addTransportDistance("customer1", "customer2", 8.0);
// Add travel times
costMatrixBuilder.addTransportTime("depot", "customer1", 12.0);
costMatrixBuilder.addTransportTime("depot", "customer2", 18.0);
costMatrixBuilder.addTransportTime("customer1", "customer2", 10.0);
VehicleRoutingTransportCosts costMatrix = costMatrixBuilder.build();
// Use custom costs in problem
VehicleRoutingProblem problem = VehicleRoutingProblem.Builder.newInstance()
.setRoutingCost(costMatrix)
.addVehicle(vehicle)
.addJob(delivery1)
.addJob(delivery2)
.build();
// Or implement custom transport costs class
VehicleRoutingTransportCosts customCosts = new VehicleRoutingTransportCosts() {
@Override
public double getTransportCost(Location from, Location to,
double departureTime, Driver driver, Vehicle vehicle) {
// Calculate cost based on distance and vehicle type
double distance = calculateDistance(from, to);
return distance * vehicle.getType().getVehicleCostParams().perDistanceUnit;
}
@Override
public double getTransportTime(Location from, Location to,
double departureTime, Driver driver, Vehicle vehicle) {
// Calculate time (could include traffic patterns)
return calculateDistance(from, to) / 50.0; // Assume 50 km/h
}
private double calculateDistance(Location from, Location to) {
double dx = from.getCoordinate().getX() - to.getCoordinate().getX();
double dy = from.getCoordinate().getY() - to.getCoordinate().getY();
return Math.sqrt(dx * dx + dy * dy);
}
};
```
## StateManager and ConstraintManager - Add Custom Constraints
Define custom hard and soft constraints to model complex business rules.
```java
// Create state and constraint managers
StateManager stateManager = new StateManager(problem);
ConstraintManager constraintManager = new ConstraintManager(problem, stateManager);
// Add a hard route constraint: Vehicle "van1" cannot serve "customer5"
final Job restrictedJob = problem.getJobs().get("customer5");
HardRouteConstraint vehicleRestriction = new HardRouteConstraint() {
@Override
public boolean fulfilled(JobInsertionContext context) {
if (context.getNewVehicle().getId().equals("van1")) {
// Check if trying to insert restricted job or route already contains it
if (context.getJob() == restrictedJob ||
context.getRoute().getTourActivities().servesJob(restrictedJob)) {
return false;
}
}
return true;
}
};
constraintManager.addConstraint(vehicleRestriction);
// Add a soft route constraint: Prefer certain vehicles for certain jobs
SoftRouteConstraint vehiclePreference = new SoftRouteConstraint() {
@Override
public double getCosts(JobInsertionContext context) {
// Add penalty if preferred vehicle not used
if (context.getJob().getId().startsWith("priority_") &&
!context.getNewVehicle().getId().equals("express_van")) {
return 100.0; // Penalty for using non-preferred vehicle
}
return 0.0;
}
};
constraintManager.addConstraint(vehiclePreference);
// Build algorithm with custom constraints
VehicleRoutingAlgorithm algorithm = Jsprit.Builder.newInstance(problem)
.setStateAndConstraintManager(stateManager, constraintManager)
.buildAlgorithm();
Collection solutions = algorithm.searchSolutions();
```
## SolutionPrinter - Display Solution Results
Print formatted solution summaries and detailed route information to console.
```java
// Print concise summary
SolutionPrinter.print(problem, bestSolution, SolutionPrinter.Print.CONCISE);
/*
Output:
+--------------------------+
| problem |
+---------------+----------+
| indicator | value |
+---------------+----------+
| nJobs | 10 |
| nServices | 8 |
| nShipments | 2 |
| fleetsize | INFINITE |
+--------------------------+
+----------------------------------------------------------+
| solution |
+---------------+------------------------------------------+
| indicator | value |
+---------------+------------------------------------------+
| costs | 245.67 |
| nVehicles | 3 |
+----------------------------------------------------------+
*/
// Print verbose output with route details
SolutionPrinter.print(problem, bestSolution, SolutionPrinter.Print.VERBOSE);
/*
Additional output showing each route:
+----------------------------------------------------------------+
| detailed solution |
+---------+------------+-------------+----------+--------+--------+
| route | vehicle | activity | job | arrTime| endTime|
+---------+------------+-------------+----------+--------+--------+
| 1 | vehicle1 | start | - | undef | 0 |
| 1 | vehicle1 | service | cust1 | 15 | 25 |
| 1 | vehicle1 | service | cust3 | 40 | 50 |
| 1 | vehicle1 | end | - | 65 | undef |
+---------+------------+-------------+----------+--------+--------+
*/
```
## Plotter - Visualize Problems and Solutions
Generate PNG images showing routes, customers, and depots on a 2D plot.
```java
// Plot just the problem (shows locations without routes)
new Plotter(problem).plot("output/problem.png", "My VRP Problem");
// Plot the solution with routes
new Plotter(problem, bestSolution).plot("output/solution.png", "Optimized Routes");
// Customize plotter settings
Plotter plotter = new Plotter(problem, bestSolution);
plotter.setLabel(Plotter.Label.ID); // Label points with job IDs
plotter.setShowFirstActivity(true); // Mark first activity in each route
plotter.plot("output/labeled_solution.png", "Labeled Solution");
// Plot with size labels (shows capacity dimensions)
Plotter sizePlotter = new Plotter(problem, bestSolution);
sizePlotter.setLabel(Plotter.Label.SIZE);
sizePlotter.plot("output/size_solution.png", "Solution with Demand Labels");
```
## GraphStreamViewer - Interactive Route Visualization
Display an interactive, animated visualization of the routing solution using GraphStream.
```java
// Create interactive viewer with animation
new GraphStreamViewer(problem, bestSolution)
.setRenderDelay(100) // Delay between rendering each activity (ms)
.setRenderShipments(true) // Show shipment pickup-delivery connections
.setGraphStreamFrameScalingFactor(2.0)
.display();
// Viewer with custom settings for large problems
GraphStreamViewer viewer = new GraphStreamViewer(problem, bestSolution);
viewer.labelWith(GraphStreamViewer.Label.ID);
viewer.setRenderDelay(50);
viewer.display();
```
## Algorithm Listeners - Monitor Algorithm Progress
Add listeners to track algorithm execution, log progress, or implement custom logic.
```java
// Listen to iteration events
algorithm.addListener(new IterationStartsListener() {
@Override
public void informIterationStarts(int iteration, VehicleRoutingProblem problem,
Collection solutions) {
if (iteration % 100 == 0) {
VehicleRoutingProblemSolution best = Solutions.bestOf(solutions);
System.out.println("Iteration " + iteration + " - Best cost: " + best.getCost());
}
}
});
// Listen to algorithm completion
algorithm.addListener(new AlgorithmEndsListener() {
@Override
public void informAlgorithmEnds(VehicleRoutingProblem problem,
Collection solutions) {
System.out.println("Algorithm finished!");
System.out.println("Final solution cost: " + Solutions.bestOf(solutions).getCost());
}
});
// Track search progress with chart listener (requires jsprit-analysis)
AlgorithmSearchProgressChartListener chartListener =
new AlgorithmSearchProgressChartListener("output/progress.png");
algorithm.addListener(chartListener);
// Run algorithm with listeners
Collection solutions = algorithm.searchSolutions();
```
## Multiple Depot VRP - Assign Jobs to Multiple Depots
Model problems where vehicles operate from different depot locations.
```java
// Define multiple depots by creating vehicles at different locations
VehicleType type = VehicleTypeImpl.Builder.newInstance("truck")
.addCapacityDimension(0, 100)
.setCostPerDistance(1.0)
.build();
// Depot 1 vehicles
VehicleImpl depot1_v1 = VehicleImpl.Builder.newInstance("depot1_vehicle1")
.setStartLocation(Location.newInstance(10, 10))
.setType(type)
.build();
VehicleImpl depot1_v2 = VehicleImpl.Builder.newInstance("depot1_vehicle2")
.setStartLocation(Location.newInstance(10, 10))
.setType(type)
.build();
// Depot 2 vehicles
VehicleImpl depot2_v1 = VehicleImpl.Builder.newInstance("depot2_vehicle1")
.setStartLocation(Location.newInstance(50, 50))
.setType(type)
.build();
VehicleImpl depot2_v2 = VehicleImpl.Builder.newInstance("depot2_vehicle2")
.setStartLocation(Location.newInstance(50, 50))
.setType(type)
.build();
// Build problem with finite fleet
VehicleRoutingProblem multiDepotProblem = VehicleRoutingProblem.Builder.newInstance()
.addVehicle(depot1_v1)
.addVehicle(depot1_v2)
.addVehicle(depot2_v1)
.addVehicle(depot2_v2)
.addJob(Service.Builder.newInstance("c1").setLocation(Location.newInstance(15, 20))
.addSizeDimension(0, 10).build())
.addJob(Service.Builder.newInstance("c2").setLocation(Location.newInstance(45, 55))
.addSizeDimension(0, 15).build())
// Add more jobs...
.setFleetSize(VehicleRoutingProblem.FleetSize.FINITE)
.build();
VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(multiDepotProblem);
VehicleRoutingProblemSolution solution = Solutions.bestOf(algorithm.searchSolutions());
```
## Heterogeneous Fleet - Mix Vehicle Types
Optimize fleet composition with different vehicle types having different costs and capacities.
```java
// Define vehicle types with different characteristics
VehicleTypeImpl smallType = VehicleTypeImpl.Builder.newInstance("small")
.addCapacityDimension(0, 50)
.setFixedCost(50.0)
.setCostPerDistance(0.5)
.build();
VehicleTypeImpl mediumType = VehicleTypeImpl.Builder.newInstance("medium")
.addCapacityDimension(0, 100)
.setFixedCost(100.0)
.setCostPerDistance(0.8)
.build();
VehicleTypeImpl largeType = VehicleTypeImpl.Builder.newInstance("large")
.addCapacityDimension(0, 200)
.setFixedCost(200.0)
.setCostPerDistance(1.2)
.build();
// Create vehicles of each type at depot
Location depot = Location.newInstance(25, 25);
VehicleImpl small1 = VehicleImpl.Builder.newInstance("small1")
.setStartLocation(depot).setType(smallType).build();
VehicleImpl medium1 = VehicleImpl.Builder.newInstance("medium1")
.setStartLocation(depot).setType(mediumType).build();
VehicleImpl large1 = VehicleImpl.Builder.newInstance("large1")
.setStartLocation(depot).setType(largeType).build();
// Build problem - algorithm will optimize which vehicles to use
VehicleRoutingProblem problem = VehicleRoutingProblem.Builder.newInstance()
.addVehicle(small1)
.addVehicle(medium1)
.addVehicle(large1)
.addAllJobs(createJobs()) // Your jobs
.setFleetSize(VehicleRoutingProblem.FleetSize.INFINITE) // Can use multiple of each
.build();
// Use algorithm that considers fixed costs
VehicleRoutingAlgorithm algorithm = Jsprit.Builder.newInstance(problem)
.setProperty(Jsprit.Parameter.FIXED_COST_PARAM, "1.0") // Weight for fixed costs
.buildAlgorithm();
VehicleRoutingProblemSolution solution = Solutions.bestOf(algorithm.searchSolutions());
```
## Initial Solution - Provide Starting Point
Warm-start the algorithm with an existing solution for faster convergence.
```java
// Build initial routes manually
VehicleRoute route1 = VehicleRoute.Builder.newInstance(vehicle)
.addService(service1)
.addService(service2)
.build();
VehicleRoute route2 = VehicleRoute.Builder.newInstance(vehicle2)
.addPickup(shipment1)
.addDelivery(shipment1)
.build();
// Create initial solution
VehicleRoutingProblemSolution initialSolution = new VehicleRoutingProblemSolution(
Arrays.asList(route1, route2),
1000.0 // Initial cost estimate
);
// Add to algorithm as starting point
VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(problem);
algorithm.addInitialSolution(initialSolution);
// Or add locked routes to the problem (cannot be modified by algorithm)
VehicleRoutingProblem lockedProblem = VehicleRoutingProblem.Builder.newInstance()
.addVehicle(vehicle)
.addJob(service1)
.addJob(service2)
.addInitialVehicleRoute(route1) // This route is locked
.build();
```
## Summary
jsprit provides a comprehensive solution for vehicle routing optimization, supporting everything from simple delivery routes to complex multi-depot, heterogeneous fleet scenarios with time windows and pickup-delivery constraints. The library's strength lies in its flexible builder-based API that allows incremental problem construction, combined with a powerful adaptive large neighborhood search algorithm that can be customized through parameters or extended with custom constraints and objective functions.
For integration into production systems, jsprit offers multiple extension points: custom transport cost implementations for real-world routing engines, constraint managers for business rules, algorithm listeners for progress monitoring, and visualization tools for solution analysis. The library works well with standard Java build tools and can be parallelized for improved performance on large-scale problems. Whether used for tactical fleet planning or operational route optimization, jsprit delivers high-quality solutions while maintaining the flexibility needed for real-world logistics applications.