Setting Up Datadog APM with the Java Agent: Tracing Without Code Changes
Attach the Datadog Java Agent with -javaagent, configure unified service tags and sampling, add custom spans with @Trace, and verify traces end to end in the Trace Explorer.
10 Mar 2026, 16:22 UTC

The outcome you want
You have a Java service (or several) and no visibility into where request latency goes. The goal: distributed traces flowing into Datadog APM, showing each request broken into spans across frameworks and downstream services, without rewriting application code.
The Datadog Java Agent makes this possible through bytecode instrumentation. It attaches to the JVM at startup via the -javaagent flag and automatically instruments common libraries — Spring, Tomcat, JDBC, Kafka, gRPC, and many HTTP clients. Distributed tracing works through propagation headers (notably dd-trace-id) injected into outbound requests, so a single request can be followed across microservices.
The instructions below assume a recent JVM (Java 8+; Java 11 or 17 recommended), a current dd-java-agent release, and a Datadog Agent reachable on the host or as a sidecar. Version-specific behavior changes between agent releases, so verify against the release notes for the version you download.
Prerequisites
- The Datadog Agent (v6 or v7) running on the host, in the same container, or as a sidecar/daemonset. APM listens on port 8126 by default and must be enabled in the Agent configuration.
- The dd-java-agent.jar downloaded from the official Maven repository or GitHub releases. Pin a specific version rather than using
latestin production. - A valid DD_API_KEY configured on the Datadog Agent (not the Java app itself).
- Ability to modify your application's JVM startup arguments and restart it.
Attach the agent and configure the service
The most common failure is incorrect JVM argument placement: -javaagent must appear before -jar or the main class. Add it to your startup command:
java -javaagent:/opt/datadog/dd-java-agent.jar \
-Ddd.service=orders-api \
-Ddd.env=production \
-Ddd.version=1.4.2 \
-jar orders-api.jarThe three unified service tagging properties (dd.service, dd.env, dd.version) matter more than they look — they drive filtering, deployment tracking, and correlation with logs and infrastructure metrics. Prefer environment variables in containerized deployments:
ENV DD_SERVICE=orders-api
ENV DD_ENV=production
ENV DD_VERSION=1.4.2
ENV DD_TRACE_AGENT_URL=http://localhost:8126
CMD ["java", "-javaagent:/app/dd-java-agent.jar", "-jar", "app.jar"]In Kubernetes, run the Datadog Agent as a DaemonSet and point the app at the node IP via the downward API, or use the Datadog Admission Controller to inject the library automatically — but note that admission-controller injection changes how you pin and roll out agent versions.
Control sampling before it controls your bill
By default the agent applies rate limits to avoid overwhelming the intake. In high-throughput services, set an explicit sampling rate to balance observability against CPU overhead and egress cost:
-Ddd.trace.sample.rate=0.5A value of 0.5 keeps roughly half of traces. Start lower (0.1–0.3) on very hot services and raise it for low-traffic ones. Be aware that head-based sampling drops whole traces, which can hide rare errors — error spans are generally retained, but verify this behavior for your agent version. Also review trace scrubbing: spans can capture query strings, request bodies, or database statements containing sensitive data. Configure obfuscation rules (for example, DD_TRACE_HTTP_SERVER_TAG_QUERY_STRING controls and resource/span filtering) before going to production.
Add custom spans where auto-instrumentation stops
Auto-instrumentation covers frameworks, not your business logic. For a critical internal method, annotate it:
import datadog.trace.api.Trace;
public class PricingEngine {
@Trace(operationName = "pricing.calculate")
public Quote calculate(Cart cart) { ... }
}This requires the dd-trace-api dependency (provided scope is fine — the agent supplies it at runtime). For finer control, use the Tracer API to create spans and add tags such as customer tier or cart size, which then become searchable facets in the Trace Explorer.
Verify the pipeline end to end
- Startup logs: check application logs for the Datadog tracer initialization banner. Silence or a ClassNotFound-style error usually means the
-javaagentpath is wrong or misplaced. - Agent connectivity: from the app container/host, confirm port 8126 is reachable (
curl -v http://localhost:8126/infoshould return Agent info when APM is enabled). - Datadog UI: open APM → Services and confirm your
dd.servicename appears within a few minutes of traffic. - Live trace: execute a real request through the system, then search the Trace Explorer for the service. For cross-service setups, confirm the trace shows spans from both services joined under one trace ID — if each service shows isolated traces, header propagation is broken (often a proxy stripping headers or an uninstrumented HTTP client).
Recovery and known limits
Because the agent only attaches at JVM startup, rollback is simple: remove the -javaagent argument and restart. No application state changes. If startup fails after adding the agent, check for JVM/agent version incompatibility first, and run with -Ddd.trace.debug=true temporarily for verbose diagnostics (disable afterward — it is noisy). Known limitations: auto-instrumentation does not cover every library, reactive frameworks can produce fragmented traces on older agent versions, and the agent adds measurable (usually low single-digit percent) CPU and memory overhead — measure on your own workload rather than trusting any published figure.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.