Enabling Distributed Tracing for Java Microservices with the New Relic Agent
Get end-to-end traces across Java microservices with the New Relic agent: install steps, verification checks, missing-span diagnostics, and sampling controls for production.
12 Oct 2026, 23:05 UTC

The problem: requests vanish between services
When a request crosses three or four Java services, a slow response in the first service tells you almost nothing about where the time went. Distributed tracing solves this by attaching a unique trace ID to the request and propagating it through HTTP headers — either W3C Trace Context headers or New Relic's own — so every service's work appears as spans in one end-to-end trace.
The useful takeaway: for most Spring Boot or Jakarta EE stacks, you do not need to change application code. The New Relic Java agent auto-instruments common frameworks and HTTP clients. Your job is to install the agent consistently on every participating service, confirm the trace ID actually propagates, and set sampling limits before you ship to a high-traffic environment.
Prerequisites
- A New Relic account and an ingest license key.
- The New Relic Java agent JAR (a recent 8.x release at the time of writing; check the agent release notes for your JVM version support — Java 8 through current LTS releases are generally covered).
- Access to modify the JVM startup arguments for every service in the call chain. Tracing one service out of four gives you fragments, not a trace.
- Outbound HTTPS access from each host to New Relic's collector endpoints. If you run an egress firewall, allowlist the collector hosts for your region (US or EU) before starting.
Install and configure the agent
Unpack the agent distribution on each host. It contains newrelic.jar and a template newrelic.yml. Edit the YAML with at minimum:
common: &default_settings
license_key: 'YOUR_INGEST_LICENSE_KEY'
app_name: 'orders-api'
distributed_tracing:
enabled: trueUse a distinct, stable app_name per service — this is what appears as a node in the service map. Distributed tracing is enabled by default in current agent versions, but setting it explicitly protects you against inherited config files that disabled it.
Add the agent to the JVM startup. Run this wherever you control the service's launch command (systemd unit, Dockerfile ENTRYPOINT, or startup script); no special OS privileges are needed beyond what the service already runs as:
java -javaagent:/opt/newrelic/newrelic.jar \
-jar /app/orders-api.jarIn a container, copy the agent directory into the image and reference an absolute path. A relative path that works on a VM often breaks in a container with a different working directory.
Confirm the agent started and traces link up
Three checks, in order:
- Startup log. The agent logs a line like "New Relic Java Agent is starting" (and later a "connected" message) to its own log file in the agent directory and to stdout. If you see neither, the
-javaagentflag was not applied — check for a second startup script overriding yours. - Per-service reporting. In the New Relic UI, confirm each
app_nameappears under APM. A missing service means a license key, network, or naming problem on that host, and traces will break at that hop. - End-to-end trace. Execute a real cross-service request, then open the Distributed Tracing explorer and find the trace. You should see spans from each service with the same trace ID, plus the service map showing directional edges between caller and callee.
When spans go missing
The most common causes, roughly in order of frequency:
- A middle service has no agent. Trace context dies at the uninstrumented hop. The fix is installation, not configuration.
- Agent version skew. Very old agents predate W3C Trace Context support and may not interoperate cleanly with newer ones. Standardize on one recent version across services.
- Custom async code. If you hand work to a raw thread pool or a reactive pipeline the agent doesn't recognize, the trace context doesn't follow. Use the agent API's
@Trace(dispatcher = true)annotation or token-based async linking to bridge those gaps. - Header-stripping proxies. Some gateways or service meshes drop unknown HTTP headers. Verify
traceparentandtracestatesurvive the hop — a simplecurl -vagainst the downstream service through the proxy shows what arrives.
Control sampling before production
In high-volume services, tracing every request inflates ingest costs and adds overhead. The agent samples by default (a per-minute span reservoir), and you can tune it in newrelic.yml:
distributed_tracing:
enabled: true
sampler:
adaptive_sampling_target: 10Start conservative, verify traces still appear for your key endpoints, then raise the target if you need more coverage. Note that adaptive sampling decisions are made at the trace head, so downstream services inherit the upstream decision — configure the entry-point service first.
Rollback
Installing the agent changes runtime state only. To back out, remove the -javaagent flag and restart the JVM; the application returns to its previous behavior with no residual changes. Keep the agent directory in place if you plan to re-enable, but delete stored license keys from shared config management if the trial ends.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.