GraphHopper is a Java routing engine that imports OpenStreetMap road data into a memory-mapped graph and serves routing, isochrone, geocoding, and map-matching via a REST API. Logistics companies, mapping platforms, and OSM communities self-host GraphHopper to avoid cloud routing fees while retaining full data control. But a Java routing server brings its own failure modes: JVM heap exhaustion, slow garbage collection, stale OSM graph data, and per-profile routing failures. Vigilmon monitors GraphHopper's endpoints, latency, and infrastructure health so you catch problems before they reach your users.
What You'll Set Up
- HTTP uptime monitor for GraphHopper server health
- Route endpoint latency checks per vehicle profile
- Isochrone endpoint latency monitoring
- JVM heap utilization tracking
- Photon geocoder health check (if deployed)
- Graph data freshness heartbeat
- Matrix endpoint latency monitoring
Prerequisites
- GraphHopper 7.0+ running as a Java server (accessible over HTTP)
- At least one vehicle profile configured (car, bike, foot)
- A free Vigilmon account
Step 1: Monitor GraphHopper Server Health
GraphHopper exposes a health endpoint and responds to requests at the API root. Start with a health check that confirms the server is running and the graph is loaded:
- Log in to vigilmon.online and click Add Monitor.
- Set Type to
HTTP / HTTPS. - URL:
http://graphhopper.yourdomain.com:8989/health(orhttp://graphhopper.yourdomain.com:8989/if your version does not expose/health) - Check interval:
1 minute. - Expected HTTP status:
200. - Click Save.
For a deeper application check, GraphHopper's /info endpoint returns the loaded profiles, graph size, and build date:
GET /info
Add a second monitor against /info with Response must contain set to "profiles" to confirm the routing graph loaded successfully and at least one profile is available.
Step 2: Monitor Route Request Latency Per Vehicle Profile
GraphHopper supports multiple vehicle profiles (car, bike, foot, motorcycle). Each profile uses a separate Contraction Hierarchies (CH) graph. A profile can fail to load or degrade independently.
Add a routing monitor for each critical profile:
Car profile
- Click Add Monitor → HTTP / HTTPS.
- URL:
http://graphhopper.yourdomain.com:8989/route?point=52.5170,13.3888&point=52.5296,13.3976&profile=car&locale=en&calc_points=false
- Check interval:
2 minutes. - Expected HTTP status:
200. - Response must contain:
"paths". - Response time alert:
200ms. - Save.
Repeat for profile=bike and profile=foot with the same coordinates. GraphHopper with CH optimization returns most routes in under 50ms — a p95 above 200ms indicates either the CH index was not built for that profile, the JVM is under GC pressure, or the graph is being accessed from disk rather than memory.
Use coordinates within the road network you actually imported. Querying outside the imported region returns an error, not a routing result.
Step 3: Monitor Isochrone Calculation Latency
The /isochrone endpoint computes reachability polygons — which areas can be reached in X minutes from a starting point. Isochrone calculations are more expensive than point-to-point routing and can reveal resource pressure that simple /route checks miss.
- Click Add Monitor → HTTP / HTTPS.
- URL:
http://graphhopper.yourdomain.com:8989/isochrone?point=52.5170,13.3888&profile=car&time_limit=600
(time_limit=600 = 10-minute isochrone)
3. Check interval: 5 minutes.
4. Expected HTTP status: 200.
5. Response must contain: "polygons".
6. Response time alert: 2000ms.
7. Save.
A 10-minute driving isochrone from a central location typically returns in under 500ms. Above 2 seconds indicates JVM GC pressure, graph swap, or an unusually large polygon computation.
Step 4: Monitor JVM Heap Utilization
GraphHopper relies on Java NIO memory-mapped files for its graph, but the JVM heap still matters — object allocation during request handling, CH query structures, and isochrone polygon computation all use heap. A heap above 80% triggers aggressive GC, causing latency spikes and potential OOM crashes.
Add a cron heartbeat that checks JVM heap via the GraphHopper metrics endpoint (if enabled) or via JMX:
#!/bin/bash
# Using GraphHopper's built-in metrics (if Dropwizard metrics enabled)
METRICS=$(curl -s http://graphhopper.yourdomain.com:8989/metrics)
# Extract heap usage (adjust jq path to match your GraphHopper version)
HEAP_USED=$(echo "$METRICS" | jq '.gauges["jvm.memory.heap.used"].value')
HEAP_MAX=$(echo "$METRICS" | jq '.gauges["jvm.memory.heap.max"].value')
HEAP_PCT=$((HEAP_USED * 100 / HEAP_MAX))
if [ "$HEAP_PCT" -gt 80 ]; then
echo "GraphHopper JVM heap ${HEAP_PCT}% — GC pressure risk"
exit 1
fi
curl -s "https://vigilmon.online/api/heartbeat/YOUR_HEARTBEAT_TOKEN"
If GraphHopper metrics are not enabled, read heap via the Java process:
#!/bin/bash
GH_PID=$(pgrep -f graphhopper)
if [ -z "$GH_PID" ]; then
echo "GraphHopper process not found"
exit 1
fi
# Use jstat to check GC heap usage
HEAP_INFO=$(jstat -gc $GH_PID | tail -1)
# S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
OC=$(echo $HEAP_INFO | awk '{print $9}') # Old gen capacity (KB)
OU=$(echo $HEAP_INFO | awk '{print $10}') # Old gen used (KB)
HEAP_PCT=$((OU * 100 / OC))
if [ "$HEAP_PCT" -gt 80 ]; then
echo "JVM old gen ${HEAP_PCT}%"
exit 1
fi
curl -s "https://vigilmon.online/api/heartbeat/YOUR_HEARTBEAT_TOKEN"
Create a Cron Heartbeat in Vigilmon with a 5-minute interval.
Step 5: Monitor Graph Import Freshness
GraphHopper imports OpenStreetMap PBF data into a memory-mapped graph stored on disk. This graph ages as the road network evolves. Weekly imports are common for production deployments.
#!/bin/bash
GRAPH_DIR="/data/graphhopper/graph-cache"
MAX_AGE_DAYS=7
# GraphHopper writes a timestamp file or you can check the directory modification time
MOD_TIME=$(stat -c %Y "$GRAPH_DIR")
NOW=$(date +%s)
AGE_DAYS=$(( (NOW - MOD_TIME) / 86400 ))
if [ "$AGE_DAYS" -gt "$MAX_AGE_DAYS" ]; then
echo "GraphHopper graph is ${AGE_DAYS} days old (max: ${MAX_AGE_DAYS})"
exit 1
fi
curl -s "https://vigilmon.online/api/heartbeat/YOUR_HEARTBEAT_TOKEN"
Create a Cron Heartbeat with a 24-hour interval. The /info endpoint also reports the data_date field (when the OSM data was from), which you can check programmatically:
DATA_DATE=$(curl -s http://graphhopper.yourdomain.com:8989/info | jq -r '.data_date')
echo "Graph data date: $DATA_DATE"
Step 6: Monitor Photon Geocoder Health (If Deployed)
Many GraphHopper deployments include Photon — an open source geocoder that runs on Elasticsearch and provides forward and reverse geocoding. If Photon is part of your stack, add a separate health check:
- Click Add Monitor → HTTP / HTTPS.
- URL:
http://photon.yourdomain.com:2322/api?q=berlin&limit=1(Replace with your Photon host and a known geocodable query) - Check interval:
2 minutes. - Expected HTTP status:
200. - Response must contain:
"features". - Response time alert:
500ms. - Save.
Also monitor Photon's Elasticsearch dependency:
GET http://localhost:9200/_cluster/health
Response must contain: "status":"green" or "yellow" (avoid alerting on yellow if you run a single-node Elasticsearch, which is always yellow).
Step 7: Monitor /matrix Endpoint Latency
The /matrix endpoint computes distance and time matrices between multiple origin-destination pairs. Matrix calculations scale quadratically with point count, making them the most resource-intensive GraphHopper requests.
- Click Add Monitor → HTTP / HTTPS.
- Use a small matrix request body (POST):
POST /matrix
{
"from_points": [
[13.3888, 52.5170],
[13.3976, 52.5296]
],
"to_points": [
[13.3888, 52.5170],
[13.3976, 52.5296]
],
"profile": "car",
"out_arrays": ["times"]
}
- Check interval:
5 minutes. - Expected HTTP status:
200. - Response time alert:
1000msfor a 2×2 matrix (scale threshold with typical matrix size in your application). - Save.
Step 8: Configure Alerting
In Vigilmon, configure alert thresholds and notification channels:
| Monitor | Alert Condition | Severity |
|---|---|---|
| Server health (/health) | Status ≠ 200 | Critical |
| /info profile check | profiles missing from response | Critical |
| /route car profile | Status ≠ 200 or time >200ms | High |
| /route bike/foot profiles | Status ≠ 200 or time >200ms | Medium |
| /isochrone | Status ≠ 200 or time >2s | Medium |
| JVM heap | >80% | High |
| Graph freshness | >7 days old | Medium |
| Photon geocoder | Status ≠ 200 | High (if geocoding is required) |
| /matrix latency | p95 >1s | Low |
Route the Critical and High alerts to your on-call channel immediately. A GraphHopper crash means all routing, isochrone, and geocoding requests fail instantly — JVM OOM crashes happen without warning when heap is exhausted.
For latency alerts, use a 3-consecutive-failures window to avoid alerting on momentary GC pauses.
Conclusion
GraphHopper's flexibility — multiple vehicle profiles, isochrones, geocoding, map matching — means more moving parts to monitor. With Vigilmon, you get per-profile routing health checks to catch individual profile failures, latency tracking to detect JVM GC pressure before it becomes user-visible, heap utilization monitoring to prevent OOM crashes, and graph freshness alerts to ensure routes reflect current road data. Set up these monitors in under 20 minutes and keep your self-hosted routing infrastructure running reliably.
Get started at vigilmon.online.