Skip to main content

Native Route Object (NRO)

Native Route Object (NRO) is an alternative implementation of the route parser and route model in C++, which enables efficient parsing and efficient storage of route data in native memory.

NRO is designed to address some of the limitations of the Java route object defined in mapbox-java, including:

  • high Java heap usage, which is constrained by the per-process Java heap limit Android enforces
  • less consistent parsing performance when the Java heap is under pressure

Why adopt NRO

Adopting NRO can reduce route-related Java heap pressure in apps that work with long routes or multiple alternative routes. In these scenarios, route objects can occupy a large share of the Java heap, which increases garbage collection activity, makes route parsing less stable, and in extreme cases can lead to out-of-memory errors.

NRO and the Coordination Layer can improve performance especially well when they are adopted together. Coordination Layer UI components are partially implemented in C++ and can access the native-memory route model more directly than older UI components, which helps reduce Java heap allocations and improve efficiency.

Java Heap Consumption

The following benchmark captures Java heap consumption in a scenario where a test application built a long route by adding waypoint after waypoint through an area with a dense road network. This maximized the size of each route object and forced the Navigation SDK to handle several sets of routes at the same time.

ApproachPeak (MB)Average (MB)
Java Route Model (used by default)524311
Native Route Object (NRO)314130
NRO with Coordination layer10932

Benchmark conditions

When Java route objects are used, the Navigation SDK may need to remove current alternative routes before parsing new ones so the route objects fit in memory. When NRO is enabled, alternative routes do not need to be dropped before parsing begins.

Note that the reduction in Java heap usage comes with a trade-off: route data is now stored in native memory instead, so native memory consumption increases by a comparable amount.

Parsing speed

The NRO parser provides more stable parsing performance than the Java parser. When the Java heap is already occupied by the application, garbage collection has to pause the parser more often to reclaim temporary objects.

Java heap state before benchmarkNRO parsing time (ms)Java parser time (ms)NRO speed advantage
nearly empty1,3601,520+12%
150 MB occupied1,3452,483+85%

Benchmark conditions

Enable NRO

Starting from Android Nav SDK 3.20.0, you can enable NRO by providing NavigationOptions with nativeRouteObject(true).

NavigationOptions.Builder(context)
.nativeRouteObject(true)
// other navigation options
.build()

When you access NavigationRoute#directionsRoute, you receive a wrapper around the native object, and only the route data your app accesses is copied temporarily to the Java heap.

For most applications, the API change is small. The wrapper is mostly compatible with the Java route object API, but there are a few important nuances to review before rollout.

Compatibility considerations

Route traversal and temporary heap usage

Accessing data from native memory and copying it temporarily to the Java heap:

  • increases time to traverse route objects
  • creates short spikes in Java memory usage

For example, iterating congestion numeric annotations across all route legs is 6.5× slower with NRO:

routes.forEach {
it.directionsRoute.legs()!!.forEach {
it.annotation()!!.congestionNumeric()!!.forEach { }
}
}
ApproachTime (ms)Traversal overhead
Java Route Model2.1
Native Route Object (NRO)13.56.5× slower

Benchmark conditions

This matters most in code that repeatedly walks route legs, steps, intersections, or annotations. If you need to scan all steps or all annotations across the entire route, always do so on a background thread — the full traversal copies a large amount of data from native memory to the Java heap and can block the main thread long enough to drop frames. Wrap these code paths in trace sections so they are visible in Perfetto, and verify that processing time and Java heap usage do not regress significantly after enabling NRO.

Equality and hash behavior

Unlike Java route objects, equals() and hashCode() do not compare object contents. Instead, they compare the native-memory pointers that back each object. This is faster, but equals() can return false and hashCode() can return different values for objects with the same content.

This is especially important after route refreshes. If a refresh produces a new route object without changing the route content, equals() can still return false and hashCode() can still change because the refreshed route is backed by a different native object. The same applies to sub-objects such as voice instructions: even when their content is unchanged after refresh, comparing the old and new objects can still return false.

If your app compares refreshed route objects or their sub-objects, do not assume that unchanged content will preserve equals() or hashCode() results.

Coordinate precision

Geometry-decoding APIs can return Point coordinates whose latitude/longitude values differ from the non-NRO implementation by a small amount. The NRO and non-NRO code paths use different polyline decoders (one written in C++, the other in Java), and small algorithmic differences produce slightly different rounding in the trailing digits. The two values describe the same point on the road to well under a millimeter, but they are not guaranteed to be bit-identical.

This affects any API that returns coordinates derived from the route's encoded polyline geometry — for example, DirectionsRoute.completeGeometryToPoints() (via DecodeUtils) — as well as values computed downstream from them (route line rendering, camera framing, custom map-matching helpers, etc.). Other coordinate-bearing fields that come directly from the JSON response — such as DirectionsWaypoint.location(), StepManeuver.location(), and StepIntersection.location() — and scalar fields such as distance(), duration(), weight(), etc. are not affected.

If your code compares coordinates across the NRO and non-NRO paths (tests, caches, equality checks, etc.), use an epsilon-based comparison rather than exact Double equality.

JSON and builder workflows

Converting route objects and their sub-objects to JSON is not supported with NRO. Rebuilding them through toBuilder() is also not supported.

If your integration serializes route objects, clones them, or patches them on the client side, plan an alternative workflow before enabling NRO.

Client-side route object updates

Features that rely on client-side route object updates are now disabled:

Before enabling NRO, verify that your app does not depend on these behaviors.

Route lifetime and memory retention

With NRO, this is more consequential than with the Java route model. In the Java model, route objects and sub-objects are independent Java objects: keeping a reference to a sub-object such as a voice instruction retains only that instruction in memory, while the rest of the route can be garbage collected. With NRO, sub-objects are views into the same underlying native memory block, so a reference to any one of them — even a small sub-object — keeps the entire native route alive in memory.

For this reason, do not keep references to route objects or sub-objects after the Navigation SDK removes them from the current routes. Although NavigationRoute instances may seem lightweight, they still hold a reference to a large native memory block. The same applies to sub-objects: even a small object such as a voice instruction can prevent the whole native route from being released. Release all references promptly after routes are replaced so native memory can be reclaimed.

Unrecognized properties

Accessing unrecognized properties on route objects is especially inefficient with NRO. Each access triggers a full recursive copy of the accessed field from native memory to the Java heap. For primitive fields this is negligible, but for objects and arrays the copy happens on every access. Avoid accessing unrecognized properties in hot paths, and cache the result if you need to read the same field more than once.

Benchmark conditions

All measurements were collected with automated tests or macrobenchmarks using Navigation SDK version 3.20.0 on a Google Pixel 6 running Android 16.

Was this page helpful?