Open-source project
graphhopper/jsprit avatar
graphhopper/jsprit

jsprit 2.0 splits ruin from insertion so you can weight them yourself

jsprit is a java based, open source toolkit for solving rich vehicle routing problems

1,834 stars640 forksJavaApache-2.0

At a glance

What is it?
jsprit is GraphHopper's Java toolkit for rich vehicle routing problems, built around a single meta heuristic and a builder API. Version 2.0 moved from one opaque algorithm factory to independently weighted ruin and insertion strategies, which is the change that decides how much work your problem deserves.
Who is it for?
Adopt jsprit when your routing needs go beyond capacity and distance, since time windows, pickups and deliveries, multiple depots, heterogeneous fleets and dial a ride all sit behind the same builder. Do not adopt it if you need a route server rather than a library, because it computes solutions in process and returns Java objects with no transport layer.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 39 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One meta heuristic, many problem shapes

The design is unusual enough to state plainly: jsprit is built on a single all purpose meta heuristic, and variety comes from the problem definition rather than from separate solvers.

What that single machinery currently covers is a long list. Capacitated VRP is the base case. Multiple Depot VRP adds several depots. Time Windows constrain when a job may be served. Backhauls model a return leg carrying nothing. Pickups and Deliveries pair a collection with a later drop. Heterogeneous Fleet means vehicles with different types, which is where the capacity dimensions become more than one. Time dependent VRP makes travel times vary with the clock, and Dial a Ride applies to passengers rather than goods. The list also says various combinations of these types, which is the point: constraints compose in the builder rather than selecting a different algorithm.

Traveling Salesman Problem is on that list too, as the degenerate case with one vehicle and no capacity constraint.

The claim about being lightweight, flexible and easy to use is paired with a claim about architecture and test coverage, and the README backs that with links to docs/Meta-Heuristic.md and a Features document. Extension is a stated design goal, not an accident, which matters if your problem will eventually need a constraint nobody wrote yet.

GraphHopper maintains the project.

Java 21 and a single core artifact

The requirement is Java 21 or higher. Installation is a standard Maven or Gradle coordinate:

xml
<dependency>
    <groupId>com.graphhopper</groupId>
    <artifactId>jsprit-core</artifactId>
    <version>2.0.0</version>
</dependency>
groovy
implementation 'com.graphhopper:jsprit-core:2.0.0'

The repository is a multi module Maven build with jsprit-core and jsprit-analysis at the top level under a root pom.xml, and NOTICE.md documents the direct dependencies of each module. That split is worth understanding before you start: jsprit-analysis is the evaluation and visualisation side, and if you only want solutions you can depend on jsprit-core alone.

The documentation lives in docs/ with Home.md as the entry point. CHANGELOG.md carries the detail behind the version claims and MIGRATION.md is the file to read if you are coming from 1.x.

Licensing is Apache License v2, which for a library embedded in a commercial logistics product is about the least friction you can get, and WHATS_NEW.md sits at the root alongside the changelog.

The quick start is four builder steps

The example defines a vehicle type with one capacity dimension capped at 10, builds a vehicle of that type starting at the origin, defines two service jobs at coordinates (5, 7) and (3, 4) with sizes 1 and 2, and then assembles the problem.

java
VehicleType vehicleType = VehicleTypeImpl.Builder.newInstance("type")
    .addCapacityDimension(0, 10)
    .build();

VehicleImpl vehicle = VehicleImpl.Builder.newInstance("vehicle")
    .setStartLocation(Location.newInstance(0, 0))
    .setType(vehicleType)
    .build();

Service job1 = Service.Builder.newInstance("job1")
    .addSizeDimension(0, 1)
    .setLocation(Location.newInstance(5, 7))
    .build();

VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance()
    .addVehicle(vehicle)
    .addJob(job1)
    .addJob(job2)
    .build();

Every entity is a builder with a string name, and capacity is expressed as numbered dimensions rather than a single scalar, which is how the same code carries weight and volume.

Solving is then three lines. Jsprit.createAlgorithm(vrp) returns a VehicleRoutingAlgorithm, searchSolutions() returns a collection of solutions rather than one, and Solutions.bestOf(solutions) picks among them. The plural matters: a meta heuristic is stochastic, so the collection is where the alternative routings are, and anything you build on top should decide how to choose rather than assuming the first is good.

Locations here are plain coordinates through Location.newInstance, so this example has no road network at all. Travel time comes from your job and vehicle configuration.

Independent operator weighting is the 2.0 change that matters

In 2.0 the algorithm is assembled rather than configured through a single factory. You add ruin operators with weights that sum to a distribution, and insertion operators separately:

java
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();

The semantics read directly from the example. Three ruin strategies share the work at 0.4, 0.4 and 0.2, and two insertion strategies share it at 0.7 and 0.3. The ruin operator is the destructive half of an iterated local search, tearing part of the current solution apart; the insertion operator is the constructive half, putting work back in. Setting their weights separately is what lets a problem with expensive local moves run more insertion than ruin, or the reverse.

The named strategies differ in how they choose what to remove: radial, random and worst describe the selection rule, and on the insertion side best and regretFast describe how a job is placed, with regret insertion preferring the placement where a job is worst off.

Other 2.0 additions are listed alongside: regret-k insertion with a configurable k, an MST based cluster ruin strategy, declarative initial solutions through SolutionSpec, and a Job.Type enum for type handling instead of ad hoc checks. The release tag on that refactor is v1.9.0-beta.12, described as refactoring the scoring function.

Listening to the algorithm at runtime

The second 2.0 feature is an event system, and for a library whose search is stochastic and time bounded, observability is the difference between tuning and guessing:

java
AlgorithmEventAdapter adapter = new AlgorithmEventAdapter(algorithm);
algorithm.addListener(adapter);
algorithm.addEventListener(event -> {
    if (event instanceof StrategyExecuted e) {
        System.out.println("Strategy: " + e.strategyId());
    }
});

Two registration paths are shown and they are not interchangeable in effect. The adapter is a concrete listener you construct with the algorithm, which suggests it carries accumulated state. The lambda form is the plain functional path for anything you just want to print.

The event type shown, StrategyExecuted, exposes a strategyId, which identifies which configured ruin or insertion operator ran. With the weighted operators above, that is what tells you whether the 0.2 worst ruin is ever being selected, or whether a weight you configured is effectively dead.

Because the pattern matching on e uses the Java 21 baseline, the event API is written against modern language features rather than the older syntax.

Release history, beta tags, and what 2.0 costs to adopt

The tag list is worth reading carefully. The most recent releases are 1.9.0-beta.12 on 2024-11-19, described as refactoring the scoring function, preceded by 1.9.0-beta.11 and beta.10 in May 2023. Yet the README documents installation at 2.0.0 and a What's New in 2.0 section, which means the stable line shipped without the beta tags appearing in the recent release list on the repository page.

The practical consequence is that a 1.x user is reading a migration document for a version boundary they may have been on for a long time. Scoring function changes are exactly the kind of edit that changes results without changing signatures: a route that was optimal under the old objective may no longer be under the new one, and any regression baseline you built has to be re-measured.

The last push was on 2026-09-14, so the master branch is still moving.

What you get for a 1.x upgrade is in MIGRATION.md rather than in the README, and the release detail is in CHANGELOG.md. For anyone evaluating rather than migrating, the version to read the docs against is 2.0.0, since that is what the coordinate in the installation snippet resolves to.

Editorial conclusion

Adopt jsprit when your routing needs go beyond capacity and distance, since time windows, pickups and deliveries, multiple depots, heterogeneous fleets and dial a ride all sit behind the same builder. Do not adopt it if you need a route server rather than a library, because it computes solutions in process and returns Java objects with no transport layer. Verify first how your objective is expressed, since a 2.0 upgrade changes both the scoring function and the algorithm construction, and MIGRATION.md exists for that reason.

Frequently asked questions

What does the jsprit library solve?

Rich vehicle routing problems and the traveling salesman problem, through one meta heuristic. The listed variants are capacitated VRP, multiple depot, time windows, backhauls, pickups and deliveries, heterogeneous fleet, time dependent VRP, dial a ride, and combinations of them.

How do I add jsprit to a Maven or Gradle project?

Use coordinate com.graphhopper:jsprit-core at version 2.0.0. In Gradle that is implementation 'com.graphhopper:jsprit-core:2.0.0'. Java 21 or higher is required.

What changed in jsprit 2.0?

Ruin and insertion operators are configured independently with their own weights through Jsprit.Builder, and an algorithm event system lets you register listeners and observe events such as StrategyExecuted. Regret-k insertion, an MST cluster ruin, declarative initial solutions via SolutionSpec and a Job.Type enum also arrived.

How do I get the best solution out of jsprit?

searchSolutions() returns a collection of solutions, and Solutions.bestOf selects among them. Because the meta heuristic is stochastic, treat the choice among solutions as a decision your application makes rather than assuming the first result is the answer.

What licence is jsprit released under?

Apache License v2, stated in the README and covered by LICENSE.md at the repository root. NOTICE.md lists the direct dependencies of each module.

Official sources

  1. graphhopper/jsprit on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/graphhopper-jsprit.svg)](https://hysenlabs.com/projects/graphhopper-jsprit)

Was this useful?