Choosing Between Dynatrace OneAgent and OpenTelemetry for Java on Kubernetes
Compare Dynatrace OneAgent and OpenTelemetry Java agent for Kubernetes: automatic code-level visibility vs. vendor-neutral spans, licensing models, startup overhead, and a concrete Spring Boot verification checklist.
24 Jul 2025, 13:34 UTC

The Problem: Observability Without Code Changes vs. Vendor Neutrality
Java teams moving to Kubernetes often start with a simple question: do we instrument with Dynatrace OneAgent or the OpenTelemetry Java agent? OneAgent promises zero-code instrumentation and deep code-level visibility. OpenTelemetry promises vendor-neutral spans and a single instrumentation standard. The choice affects startup time, licensing, and how much business logic you can see in traces without writing custom attributes.
How OneAgent Injects Instrumentation
OneAgent uses LD_PRELOAD on Linux (or a Windows service) to inject bytecode at JVM startup. It automatically instruments over 200 frameworks — Spring Boot, Kafka clients, gRPC, JDBC drivers — without changing your Dockerfile or deployment manifests. By default it captures method arguments, return values, and exception details for your application packages. That data flows through a proprietary protocol to an ActiveGate and then to Dynatrace SaaS or Managed, where the Davis AI engine correlates it into PurePaths.
In Kubernetes, the Dynatrace Operator deploys OneAgent as a DaemonSet with a CSI driver that mounts the agent into each pod. You annotate a namespace or pod with instrumentation.dynatrace.com/inject: "true" and the operator mutates the pod spec to add the required volume mounts and environment variables.
What OpenTelemetry Java Agent Requires
The OpenTelemetry Java agent is a -javaagent JAR you add to the JVM command line. It instruments many of the same libraries, but context propagation for unsupported frameworks must be coded manually. Spans contain metadata (service name, span kind, HTTP status) unless you add custom attributes via the OpenTelemetry API. Baggage and correlation context are not automatic — you decide what to propagate.
On Kubernetes, the OpenTelemetry Operator manages a collector and can auto-inject the Java agent by labeling a namespace with instrumentation.opentelemetry.io/inject-java: "true". Each language still needs its own agent image or init container, so the rollout is more granular but also more moving parts.
Worked Example: Spring Boot Service with Kafka and JDBC
Consider a Spring Boot service that consumes from Kafka, processes the record, and writes to PostgreSQL via JDBC.
OneAgent PurePath
- Automatic entry point: Kafka consumer poll → your @KafkaListener method → JDBC execute.
- Code-level tab shows method arguments (the Kafka record key/value), return values, and any thrown exception with stack trace.
- No annotations, no SDK dependencies, no configuration beyond the namespace annotation.
OpenTelemetry Span
- Span kind: CONSUMER for Kafka, CLIENT for JDBC.
- Attributes:
messaging.system=kafka,db.system=postgresql,db.statement(if enabled). - Business logic method (your processor) is invisible unless you manually create a child span and add attributes like
order.idorcustomer.tier.
If you need to see the actual order ID inside the trace without changing code, OneAgent delivers it today. With OpenTelemetry you must instrument the processor method.
Trade-offs: Licensing, Startup Overhead, ARM Support
| Factor | OneAgent | OpenTelemetry |
|---|---|---|
| Licensing model | Host units (infrastructure) + DEM units (real-user) | Span/log/metric volume ingest |
| JVM startup impact | +10–30% time, +50–150 MB heap | +5–15% time, +30–80 MB heap |
| ARM / GraalVM native | Limited; check platform matrix | Full support via standard agent |
| Data residency | ActiveGate placement restricted by region | Collector can run anywhere, export to any OTLP endpoint |
| Correlation fidelity | Native PurePath with code-level nodes | OTLP ingest; Davis AI correlation less rich |
At high density (hundreds of pods per node) the OneAgent heap overhead can push nodes into memory pressure. Tune capture settings (disable argument capture for high-volume packages) or consider OpenTelemetry for those workloads.
Verification Steps You Can Run Today
- Confirm OneAgent DaemonSet readiness (run on any cluster node with
kubectlaccess):kubectl get pods -n dynatrace -o wide # Expect one OneAgent pod per node with STATUS Running and CSI driver pod presentRisk: If CSI driver pods are missing, code-module injection fails silently. Check operator logs.
- Compare PurePath depth: Open a trace in Dynatrace UI, click the Code level tab. Verify method-level nodes appear for your business packages (e.g.,
com.mycompany.order.*). If only framework classes show, argument capture may be disabled. - Export a trace via OTLP to Dynatrace and inspect the Distributed Traces view. Check that
service.nameandspan.kindmap to the expected service and that parent-child links match your Kafka → JDBC flow. - Project licensing: In Settings → Monitoring → Monitored technologies → Hosts, review host-unit consumption before rolling out to all namespaces.
Decision Checklist
- Do you need method arguments and return values without writing custom spans? → OneAgent.
- Must you avoid vendor lock-in or run on unsupported ARM/GraalVM platforms? → OpenTelemetry.
- Is licensing predictability (host units) easier than volume-based forecasting? → OneAgent.
- Can you tolerate 10–30% slower JVM startup? → OneAgent; otherwise OpenTelemetry.
Start with a single namespace: enable OneAgent injection, capture a representative workload, and verify the PurePath code-level tab shows your business logic. If the overhead or licensing is unacceptable, switch that namespace to OpenTelemetry and add manual spans only where you need business attributes. The Dynatrace Operator and OpenTelemetry Operator can coexist — you can migrate incrementally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.