OpenTelemetry with Spring Boot: Modern Distributed Tracing
In modern microservices architectures, tracking requests as they flow across service boundaries is critical for debugging performance bottlenecks, diagnosing failures, and maintaining system reliability. While Zipkin and Jaeger have been popular distributed tracing tools, OpenTelemetry has emerged as the new standard, offering vendor-neutral instrumentation, logs, metrics, and traces in a unified framework.
This article explores:
- Why OpenTelemetry (OTel) is replacing traditional tracing tools
- How to integrate OpenTelemetry with Spring Boot
- Best practices for distributed tracing in microservices
- Visualizing traces with Jaeger, Zipkin, and Grafana
1. Why OpenTelemetry? The Evolution Beyond Zipkin
The Limitations of Traditional Tracing (Zipkin/Jaeger)
- Vendor lock-in: Zipkin/Jaeger require specific SDKs.
- Limited correlation: Metrics, logs, and traces were separate.
- Manual instrumentation: Required explicit code changes.
How OpenTelemetry Solves These Problems
- Standardized APIs: Works across multiple backends (Jaeger, Zipkin, Prometheus, etc.).
- Auto-instrumentation: Captures traces without code changes.
- Unified Observability: Combines traces, metrics, and logs.
2. Setting Up OpenTelemetry in Spring Boot
Step 1: Add Dependencies
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | <!-- OpenTelemetry SDK & Auto-Instrumentation --> < dependency > < groupId >io.opentelemetry</ groupId > < artifactId >opentelemetry-api</ artifactId > < version >1.30.0</ version > </ dependency > < dependency > < groupId >io.opentelemetry</ groupId > < artifactId >opentelemetry-sdk</ artifactId > < version >1.30.0</ version > </ dependency > < dependency > < groupId >io.opentelemetry.instrumentation</ groupId > < artifactId >opentelemetry-spring-boot-starter</ artifactId > < version >2.1.0</ version > </ dependency > |
Step 2: Configure application.yml
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | opentelemetry: service: name: order-service traces: exporter: otlp: endpoint: http: //otel-collector :4317 # OTLP Collector logs: exporter: otlp: enabled: true metrics: exporter: otlp: enabled: true |
Step 3: Deploy an OpenTelemetry Collector
01 02 03 04 05 06 07 08 09 10 | # docker-compose.yml services: otel-collector: image: otel /opentelemetry-collector ports: - "4317:4317" # OTLP gRPC - "4318:4318" # OTLP HTTP volumes: - . /otel-config .yaml: /etc/otel-config .yaml command : [ "--config=/etc/otel-config.yaml" ] |
Step 4: Export Traces to Jaeger/Zipkin
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | # otel-config.yaml exporters: jaeger: endpoint: "jaeger:14250" tls: insecure: true zipkin: service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [jaeger, zipkin] |
3. Advanced Distributed Tracing Techniques
Custom Spans for Business Logic
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | @GetMapping ( "/process" ) public String processOrder() { Span span = Span.current(); span.setAttribute( "order.id" , "12345" ); // Custom attributes try (Scope scope = span.makeCurrent()) { // Business logic inventoryService.reserveItems(); paymentService.chargeCustomer(); return "Order processed!" ; } catch (Exception e) { span.recordException(e); // Error tracking throw e; } } |
Baggage Propagation (Cross-Service Context)
1 2 3 4 5 6 7 8 9 | // Set baggage in Service A Baggage.current() .toBuilder() .put( "user.id" , "user-123" ) .build() .makeCurrent(); // Retrieve in Service B String userId = Baggage.current().getEntryValue( "user.id" ); |
Sampling Strategies
1 2 3 4 | # application.yml opentelemetry: traces: sampler: parentbased_always_on # or "dynamic" for adaptive sampling |
4. Visualizing Traces: Jaeger vs. Zipkin vs. Grafana
Tool | Strengths | Best For |
---|---|---|
Jaeger | Powerful querying, dependency graphs | Debugging complex microservices |
Zipkin | Simple UI, lightweight | Basic tracing needs |
Grafana Tempo | Deep integration with Prometheus metrics | Full observability (logs + metrics + traces) |
5. Best Practices for Production
- Use OTLP (OpenTelemetry Protocol) instead of vendor-specific exporters.
- Enable Auto-Instrumentation for Spring Web, JDBC, Kafka, etc.
- Implement Sampling to reduce storage costs.
- Correlate Logs & Traces using
trace_id
. - Monitor Collector Performance to avoid bottlenecks.
Conclusion
OpenTelemetry provides a future-proof, vendor-neutral way to implement distributed tracing in Spring Boot microservices. By replacing Zipkin/Jaeger with OTel, teams gain:
- Auto-instrumentation (no manual code changes)
- Unified observability (traces + metrics + logs)
- Better debugging with correlated telemetry