Skip to content
Go back

OSRM vs Valhalla vs GraphHopper

By SumGuy 12 min read
OSRM vs Valhalla vs GraphHopper

The Wrong Question

Someone in a Discord I’m in asked “what’s the best open-source routing engine?” and got four different answers and an argument about Java. Classic.

Here’s the thing: there is no best one. There’s the right one for what you’re actually building. OSRM will make you feel like a genius when your QPS numbers look like a typo — until you need to change a routing profile and realize you have to rebuild the entire graph. Valhalla will handle your most deranged multi-modal routing scenario without breaking a sweat — and you’ll pay for that in raw latency. GraphHopper sits in the middle, being reasonable at everything, and doesn’t get enough credit for it.

All three use OpenStreetMap data. All three give you routes. The differences are entirely about architecture, and architecture is everything once you’re running these in production.

Let’s go through each one like we’re actually trying to build something.

OSRM: When Latency Is the Product

OSRM — Open Source Routing Machine — has one obsession: speed. The team made a hard architectural bet: trade setup complexity for query time. The result is sub-millisecond routing on continental-scale graphs. That’s not a typo and it’s not synthetic-benchmark nonsense — real routes, real hardware, single-digit milliseconds.

The mechanism is contraction hierarchies. During preprocessing, OSRM collapses the road graph by repeatedly “contracting” nodes — replacing two-edge paths through a node with a single shortcut edge. By the time you query, the graph is orders of magnitude smaller than raw OSM. The query just has to find a path through a radically pruned graph. It’s brilliant, and it is also why OSRM has opinions.

The profile system is where you feel the tradeoff. Routing profiles — car, bike, foot, or your custom variant — are defined in Lua scripts. Want to add a new vehicle type, tweak which road classes a delivery truck can use, or add a custom cost factor? Edit the Lua, then rebuild the entire contracted graph from scratch. For a USA-sized extract that rebuild is hours. For the planet, longer.

If your profile is fixed — you’re running car routing for a navigation app, or driving-time isochrones for a logistics platform that hasn’t changed in two years — this is a non-issue. You rebuild once, deploy, and forget about it. OSRM just serves routes at obscene throughput.

If your profile changes regularly, you’re going to develop a complicated relationship with your preprocessing pipeline.

The API shape is clean and minimal. A simple route request:

OSRM route request
GET /route/v1/driving/-122.4194,37.7749;-118.2437,34.0522?overview=full&geometries=geojson
{
"code": "Ok",
"routes": [{
"distance": 559432.4,
"duration": 18923.1,
"geometry": { ... }
}]
}

OSRM also exposes a /table endpoint for N×M duration matrices (great for TSP-adjacent problems), /match for map-matching GPS traces, and /trip for optimized visit ordering. The surface area is narrow and well-designed.

Hardware reality for a USA graph: plan for around 8–12 GB RAM at query time. The preprocessing artifacts (contracted graph files) sit at roughly 5–8 GB on disk. Import + preprocessing on a fast NVMe takes 2–4 hours. RAM is the main constraint — the .osrm files need to be memory-mapped at serve time.

Best for: fixed routing profiles, brutal QPS requirements, map-matching GPS data, any use case where latency is the metric that matters most.

Valhalla: The One That Does Everything

Valhalla started at Mapbox, moved to Mapzen, then Mapzen died (a moment of silence), and now it’s community-maintained under the Valhalla GitHub org. The lineage shows — this thing was built by people running a commercial routing service who needed it to be flexible enough to handle arbitrary customer requirements.

The core architectural difference from OSRM is tile-based dynamic costing. Instead of preprocessing a collapsed graph, Valhalla tiles the road network into a hierarchy (roughly: highway-level, arterial-level, local-level) and evaluates costs at query time. You don’t precompute routes — you compute costs on the fly using a costing model passed in the request.

That’s the key part: costing models are request-time parameters, not build-time commitments. You can pass different costing JSON blobs in each API call. Change the truck weight limit, the surface preference, the time-of-day traffic penalty — it all happens in the request. No graph rebuild.

This is why Valhalla handles multi-modal routing without drama. Walking to a transit stop, taking a bus, walking again. Different costing models stitched together per segment. OSRM can’t do this because the graph is fully preprocessed for a single cost model.

The latency you pay is real but not catastrophic — tens of milliseconds for typical routes, not hundreds. You’re doing more work per query, and it shows. For most real-world applications, 30–50ms routing response times are completely fine. If you’re routing one request at a time for a user who just tapped “go,” 30ms is invisible.

A Valhalla route request is more expressive:

Valhalla route request
POST /route
{
"locations": [
{"lon": -122.4194, "lat": 37.7749},
{"lon": -118.2437, "lat": 34.0522}
],
"costing": "auto",
"costing_options": {
"auto": {
"use_highways": 1.0,
"top_speed": 130
}
},
"units": "miles",
"language": "en-US"
}

Valhalla also has first-class isochrone support (time or distance reachability polygons), turn-by-turn directions with rich maneuver data, matrix routing, and an /expansion endpoint that returns the full explored graph for debugging. The API is more verbose than OSRM’s, which is the price of flexibility.

Hardware reality for a USA graph: Valhalla’s tile set for North America is around 15–20 GB on disk. Memory usage at serve time is lower than OSRM — tiles are loaded on demand, so you can run it comfortably on 4–8 GB RAM for most traffic profiles. Build time is comparable to OSRM’s, maybe slightly faster since there’s no contraction step.

One edge worth knowing: Valhalla’s build system is a CMake C++ project with a set of Python tooling around it. The Docker images are solid, but if you ever need to build from source, budget time to learn the toolchain.

Best for: changing or custom costing profiles, multi-modal routing (walk + transit + bike), isochrone generation, anything where “I need to tweak the routing model” is a thing that will happen.

GraphHopper: The Reasonable One

GraphHopper is the only Java-based engine in this comparison, and before you groan — the JVM story these days is not what it was in 2010. GraphHopper starts in seconds, GC pauses are tunable, and memory usage is predictable. The “ugh Java” reflex is mostly historical at this point.

Architecturally, GraphHopper sits between OSRM and Valhalla. It uses contraction hierarchies like OSRM, but with a more flexible profile system that doesn’t require full graph rebuilds for profile parameter changes. The profile engine is Groovy-scripted (or Java for custom implementations), and you can add what GraphHopper calls “custom models” — JSON documents that modify edge weights without rebuilding the full CH graph. That’s a meaningful win over OSRM for anyone who needs profile flexibility without paying Valhalla’s per-query compute cost.

Performance lands in Valhalla territory — tens of milliseconds for typical routes — rather than OSRM’s single-digit milliseconds. The JVM warms up over the first few requests and stays fast. If you care about cold-start latency (serverless, ephemeral containers), plan for it. If you’re running a persistent service, not your problem.

The API is ergonomic and REST-friendly:

GraphHopper route request
GET /route?point=37.7749,-122.4194&point=34.0522,-118.2437&profile=car&locale=en&calc_points=true
{
"paths": [{
"distance": 559432.4,
"time": 18923000,
"points": { ... }
}]
}

GraphHopper ships with isochrones, matrix endpoints, map matching, and a geocoding proxy. The community edition is Apache-licensed — fully open, no enterprise gating. There’s also a commercial GraphHopper Directions API hosted service if you want managed.

One licensing note worth mentioning: the GraphHopper Maps UI frontend uses an AGPL-licensed dependency, which matters if you’re embedding it in a proprietary product. The routing engine itself (the core library and server) is Apache 2.0. Read the license if it matters to your deployment.

Hardware reality for a USA graph: graph file sits around 5–10 GB on disk depending on the profile. Memory-mapped at serve time, expect 6–10 GB RAM. Build time similar to OSRM — 2–4 hours for a full USA extract on decent hardware.

GraphHopper’s documentation is some of the best in this space. If you’re onboarding a team that hasn’t worked with routing engines before, the GraphHopper docs are going to cause less suffering than the alternatives.

Best for: teams that want competitive performance without OSRM’s profile rigidity, JVM shops, projects where documentation quality and ergonomic defaults matter, custom routing models without full rebuilds.

The Flexibility Tax

Let’s talk about why OSRM is faster and what that actually means.

OSRM’s speed comes from doing almost all the work ahead of time. By the time a request arrives, the graph is already contracted, shortcuts are already computed, the only thing left is a bidirectional Dijkstra search through a tiny graph. That’s why it’s so fast.

Valhalla and GraphHopper evaluate edge costs at query time. They’re doing more work per request, on purpose, because that’s what buys you flexibility. Valhalla’s “pass the costing model in the request” feature is literally impossible to implement on top of a fully preprocessed graph — the costs are baked in at build time.

The practical size of this gap on modern hardware (your mileage will vary by region size and query complexity):

For an interactive navigation app where users are tapping “go” and waiting: all three are effectively instant. For a batch job routing 500,000 delivery stops overnight: the gap matters. For a logistics platform serving 10,000 RPS: OSRM is not optional, it’s the only conversation.

The flexibility tax only hurts when you’re trying to pay for it with throughput.

Decision Matrix

You’ve read the specs. Here’s the actual decision:

Pick OSRM if:

Pick Valhalla if:

Pick GraphHopper if:

Don’t overthink it if:

Spinning One Up Fast

Each engine has a Docker path that doesn’t require building from source.

Terminal window
# OSRM — download a PBF, extract + contract, serve
docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-extract \
-p /opt/car.lua /data/region-latest.osm.pbf
docker run -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-contract \
/data/region-latest.osrm
docker run -t -i -p 5000:5000 -v "${PWD}:/data" \
ghcr.io/project-osrm/osrm-backend osrm-routed --algorithm ch /data/region-latest.osrm
Terminal window
# Valhalla — one container handles build + serve
docker run -dt --name valhalla -p 8002:8002 \
-v $PWD/custom_files:/custom_files \
-e tile_urls="https://download.geofabrik.de/north-america-latest.osm.pbf" \
ghcr.io/gis-ops/docker-valhalla/valhalla:latest
Terminal window
# GraphHopper — official image, pass PBF via env
docker run -p 8989:8989 -v "${PWD}/data:/data" \
-e JAVA_OPTS="-Xmx8g -Xms4g" \
graphhopper/graphhopper:latest \
--url https://download.geofabrik.de/north-america-latest.osm.pbf \
--host 0.0.0.0

All three serve from the same Geofabrik PBF source. The build step is what takes time — not the server startup itself.

The Honest Wrap-Up

If you came here hoping for a clear winner, here’s the closest I’ll get: Valhalla is the right default for new projects that don’t have extreme throughput requirements, because flexibility is cheaper to add at design time than it is to bolt on later. OSRM’s profile rebuild tax tends to hit around the time the product team wants to do something reasonable — like route around unpaved roads for an e-bike app — and suddenly you’re explaining preprocessing pipelines to a product manager at 11 PM.

But if you’re building a logistics backend routing millions of delivery stops and the profile has been the same for three years, OSRM is the answer and there’s no debate. The latency and throughput numbers are real.

GraphHopper is underrated. It doesn’t have Valhalla’s multi-modal story or OSRM’s raw ceiling, but it’s the most approachable of the three and the custom model system is genuinely clever. If you’re not sure what your routing requirements will look like in six months, it’s a reasonable hedge.

All three are production-ready. All three have active communities. All three will outlive whatever commercial routing API you’re currently paying per-request to use.

Your 2 AM self will appreciate having picked the right one before the scale-up email arrives.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Boundary vs Teleport

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts