Migration Guide: jsprit 1.x to 2.0
March 26, 2026 ยท View on GitHub
This guide helps you upgrade from jsprit 1.x to version 2.0.
Prerequisites
Java 21 Required
jsprit 2.0 requires Java 21 (was Java 8). Update your build configuration:
Maven:
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
Gradle:
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
JUnit 5 for Tests
If you have tests extending jsprit test classes:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
Breaking Changes
Index Management
Why this changed: Previously, indices were stored on Job and Vehicle objects. This caused problems when you needed to solve variations of a problem - for example, testing different fleet configurations or job subsets. Reusing Job objects across multiple VRPs led to index conflicts, forcing you to recreate all objects from scratch for each variation.
What's new: Each VRP now manages its own indices internally. When you call build(), indices are assigned automatically (0 to n-1). The same Job or Vehicle objects can be safely reused across multiple VRP instances.
Example - solving problem variations:
// Define jobs once
Service job1 = Service.Builder.newInstance("job1").setLocation(loc1).build();
Service job2 = Service.Builder.newInstance("job2").setLocation(loc2).build();
// Create different problem variations reusing the same jobs
VehicleRoutingProblem vrp1 = VehicleRoutingProblem.Builder.newInstance()
.addJob(job1).addJob(job2)
.addVehicle(smallFleet)
.build();
VehicleRoutingProblem vrp2 = VehicleRoutingProblem.Builder.newInstance()
.addJob(job1).addJob(job2)
.addVehicle(largeFleet) // Different fleet
.build();
// Solve both independently - no index conflicts
Retrieving indices: Use the VRP instance to get indices:
int jobIndex = vrp.getJobIndex(job);
int vehicleIndex = vrp.getVehicleIndex(vehicle);
Impact: For most users, this is transparent. Job.getIndex() and Vehicle.getIndex() are deprecated but still work (returning the index from the last VRP they were added to).
New Features
Independent Operator Selection (Recommended)
The new API allows selecting ruin and insertion operators independently with weights:
Before (1.x):
VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(vrp);
After (2.0) - Basic:
VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(vrp);
// Works exactly as before - no changes required
After (2.0) - With Custom Operators:
VehicleRoutingAlgorithm algorithm = Jsprit.Builder.newInstance(vrp)
// Ruin operators with weights
.addRuinOperator(0.3, Ruin.radial(0.3, 0.5, 10, 70))
.addRuinOperator(0.3, Ruin.random(0.3, 0.5, 10, 70))
.addRuinOperator(0.2, Ruin.worst())
.addRuinOperator(0.2, Ruin.kruskalCluster())
// Insertion operators with weights
.addInsertionOperator(0.7, Insertion.regretFast())
.addInsertionOperator(0.3, Insertion.best())
.buildAlgorithm();
Available Ruin Strategies
| Factory Method | Description |
|---|---|
Ruin.random() | Random job removal |
Ruin.random(minFrac, maxFrac, minBound, maxBound) | Random with bounded share |
Ruin.radial() | Remove nearby jobs |
Ruin.radial(minFrac, maxFrac, minBound, maxBound) | Radial with bounded share |
Ruin.worst() | Remove jobs with highest removal benefit |
Ruin.worst(minFrac, maxFrac, minBound, maxBound) | Worst with bounded share |
Ruin.cluster() | DBSCAN-based cluster removal |
Ruin.cluster(minFrac, maxFrac, minBound, maxBound) | Cluster with bounded share |
Ruin.kruskalCluster() | MST-based cluster removal |
Ruin.string() | Remove sequences from routes |
Ruin.string(minJobs, maxJobs, minStrings, maxStrings) | String with parameters |
Ruin.timeRelated() | Remove jobs with similar time windows |
Ruin.timeRelated(minFrac, maxFrac, minBound, maxBound) | Time-related with bounded share |
Bounded fractions clamp the share to a range:
// Remove 30-50% of jobs, but at least 10 and at most 70
Ruin.random(0.3, 0.5, 10, 70)
Available Insertion Strategies
| Factory Method | Description |
|---|---|
Insertion.regretFast() | Fast regret-2 insertion (default) |
Insertion.regretFast(k, spatialFilterK, affectedJobTracking) | Configurable regret-k |
Insertion.regret() | Standard regret-2 |
Insertion.regret(k) | Standard regret-k |
Insertion.best() | Greedy best insertion |
Insertion.cheapest() | Cheapest insertion |
Insertion.positionRegret() | Position-based regret (experimental) |
Algorithm Event System
Monitor algorithm execution with the new unified event system.
Setup requires the AlgorithmEventAdapter:
VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(vrp);
// Create and register the event adapter
AlgorithmEventAdapter adapter = new AlgorithmEventAdapter(algorithm);
algorithm.addListener(adapter);
// Now add event listeners for unified events
algorithm.addEventListener(event -> {
switch (event) {
case IterationStarted e ->
System.out.println("Iteration " + e.iteration());
case StrategyExecuted e ->
System.out.println("Strategy: " + e.strategyId());
case JobInserted e ->
System.out.println("Inserted: " + e.job().getId() + " into " + e.routeId());
case JobRemoved e ->
System.out.println("Removed: " + e.job().getId());
default -> {}
}
});
Available events:
IterationStarted,IterationCompletedStrategySelected,StrategyExecutedRuinStarted,RuinCompleted,JobRemovedRecreateStarted,RecreateCompleted,JobInserted,JobUnassignedInsertionEvaluated,AcceptanceDecision
SolutionSpec for Initial Solutions
Declaratively specify initial solutions:
SolutionSpec spec = SolutionSpec.builder()
.addRoute("vehicle1", "job1", "job2", "job3")
.addRoute("vehicle2", "job4", "job5")
.build();
VehicleRoutingAlgorithm algorithm = Jsprit.Builder.newInstance(vrp)
.addInitialSolution(spec)
.buildAlgorithm();
Job Type Enum
Replace instanceof checks with the new Job.Type enum:
Before:
if (job instanceof Service) {
// handle service
} else if (job instanceof Shipment) {
// handle shipment
}
After:
switch (job.getType()) {
case SERVICE -> handleService((Service) job);
case SHIPMENT -> handleShipment((Shipment) job);
case PICKUP -> handlePickup((Pickup) job);
case DELIVERY -> handleDelivery((Delivery) job);
}
Deprecations
Replace Deprecated Classes
| Deprecated | Replacement |
|---|---|
InsertionBuilder | InsertionStrategyBuilder |
RuinRadialMultipleCenters | Ruin.radial() |
ServiceInsertionOnRouteLevelCalculator | Use standard insertion calculators |
RouteLevelActivityInsertionCostsEstimator | Use standard cost estimators |
CalculationUtils | Inline calculations or use SolutionAnalyser |
Fixed Costs Configuration
Before:
Jsprit.Builder.newInstance(vrp)
.considerFixedCosts(true)
.buildAlgorithm();
After:
// Fixed costs are now handled through the objective function
// The default objective already considers fixed costs
Jsprit.Builder.newInstance(vrp)
.buildAlgorithm();
Experimental Features
These features are available but may change in future releases:
Spatial Filtering
Limits route evaluation to nearby routes:
Insertion.regretFast(2, 5, false) // k=2, filter to 5 nearest routes
Affected-Job Tracking
Only recalculates insertion costs for jobs affected by last insertion:
Insertion.regretFast(2, 0, true) // Enable affected-job tracking
Position-Based Regret
More accurate regret calculation considering all positions:
Insertion.positionRegret()
Insertion.positionRegret(3, 3) // k=3, expand top 3 routes
Common Migration Scenarios
Scenario 1: Simple Algorithm Creation
No changes required:
// This still works exactly as before
VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(vrp);
VehicleRoutingProblemSolution solution = Solutions.bestOf(algorithm.searchSolutions());
Scenario 2: Custom Strategy Weights
Before (1.x with SearchStrategyModule):
// Complex configuration with coupled strategies
After (2.0):
VehicleRoutingAlgorithm algorithm = Jsprit.Builder.newInstance(vrp)
.addRuinOperator(0.4, Ruin.radial())
.addRuinOperator(0.4, Ruin.random())
.addRuinOperator(0.2, Ruin.worst())
.addInsertionOperator(0.7, Insertion.regretFast())
.addInsertionOperator(0.3, Insertion.best())
.buildAlgorithm();
Scenario 3: Monitoring Algorithm Progress
Before (1.x):
algorithm.addListener(new IterationStartsListener() {
@Override
public void informIterationStarts(int iteration, ...) {
System.out.println("Iteration " + iteration);
}
});
After (2.0) - Still works, plus new events:
// Old listeners still work
algorithm.addListener(new IterationStartsListener() { ... });
// New unified event system (requires adapter setup)
AlgorithmEventAdapter adapter = new AlgorithmEventAdapter(algorithm);
algorithm.addListener(adapter);
algorithm.addEventListener(event -> {
if (event instanceof StrategyExecuted e) {
System.out.println("Used: " + e.strategyId());
}
});