August 4, 2025
Distributed Tracing with OpenTelemetry in Spring Boot Microservices
Modern microservices often span across multiple services, threads, and data stores — making it difficult to trace a user request…
By Rahul Kumar
4 min read
Modern microservices often span across multiple services, threads, and data stores — making it difficult to trace a user request end-to-end when things go wrong. Traditional logging is no longer sufficient. This is where Distributed Tracing steps in, and OpenTelemetry has emerged as the vendor-neutral, cloud-native standard for tracing, metrics, and logs.
In this article, we'll explore how to instrument a Spring Boot microservice using OpenTelemetry to gain full visibility into request flows across systems.
What is Distributed Tracing?
Distributed tracing is tracing in a distributed system.
It has added layer of complexities since each microservice has a theoretical boundary and works as self sustaining system.
Distributed tracing is a technique to track a single request as it propagates through various components of a distributed system — from HTTP calls, internal service logic, database queries, to external service calls.
Each request is assigned a unique trace ID and broken into spans (units of work). Traces help answer questions like:
- Where is latency introduced?
- Which service failed?
- How long did a DB query take?
Why OpenTelemetry?
OpenTelemetry is a CNCF (Cloud Native Computing Foundation) project that standardizes the collection of:
- Traces (Distributed Tracing)
- Metrics (Performance stats)
- Logs (Event logs)
— Vendor-neutral (works with Jaeger, Zipkin, Datadog, New Relic, etc.) — Supported by Spring Boot, Micrometer, gRPC, Kafka, and more — Allows auto and manual instrumentation.
Step-by-Step: Add OpenTelemetry to Spring Boot
1. Add OpenTelemetry Dependencies (Maven)
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency><dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>For exporting to Jaeger or OTLP:
<dependency>
<groupId>io.opentelemetry.exporter</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency><dependency>
<groupId>io.opentelemetry.exporter</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>- Configure Exporter in application.properties
otel.traces.exporter=otlp
otel.metrics.exporter=none
otel.exporter.otlp.endpoint=http://localhost:4318
otel.service.name=order-serviceotel.traces.exporter=otlp
otel.metrics.exporter=none
otel.exporter.otlp.endpoint=http://localhost:4318
otel.service.name=order-service- Enable Actuator and Web Tracing
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=*<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=*4. Auto-Instrument Web Requests
Once set up, OpenTelemetry automatically:
- Generates a trace ID for incoming requests
- Captures spans for each controller method, WebClient call, DB query, etc.
- Propagates trace context across services via HTTP headers (
traceparent,baggage)
Visualizing Traces in Jaeger
Run Jaeger in Docker:
docker run -d --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 -p 4318:4318 \
jaegertracing/all-in-one:1.46docker run -d --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 -p 4318:4318 \
jaegertracing/all-in-one:1.46Visit http://localhost:16686 and search for traces using service name (e.g., order-service).
Manual Custom Spans
For fine-grained control, you can manually create spans inside your code:
@Autowired
private Tracer tracer;
public void fetchSomething() {
Span span = tracer.spanBuilder("custom-span").startSpan();
try (Scope scope = span.makeCurrent()) {
// Your custom logic
} finally {
span.end();
}
}@Autowired
private Tracer tracer;
public void fetchSomething() {
Span span = tracer.spanBuilder("custom-span").startSpan();
try (Scope scope = span.makeCurrent()) {
// Your custom logic
} finally {
span.end();
}
}Trace Context Propagation (Between Services)
Use OpenTelemetry's WebClient instrumentation to auto-propagate context:
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.filter(new WebClientTracingFilter())
.build();
}@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.filter(new WebClientTracingFilter())
.build();
}Headers like traceparent and baggage will be passed to downstream services, keeping the trace chain intact. Imagine doing it by yourself, could be tedious and buggy.
Benefits
- Auto instrumentation — Zero-code setup for common scenarios.
- Trace context propagation — View full request flow across services.
- Vendor-agnostic — Export to Jaeger, Prometheus, Datadog, etc.
- Rich observability — Combine traces with logs and metrics.
Best Practices
- Always give meaningful names to custom spans.
- Avoid too many nested spans for performance.
- Tag important metadata (userId, tenantId, etc.) with spans for better filtering.
- Combine with log correlation for even deeper analysis.
- Avoid PII data in custom spans.
What Are Traces, Logs, and Metrics? How Do They Work Together?
Observability in modern systems means knowing what's happening inside your application without stopping it. To achieve this, we use three pillars: Logs, Metrics, and Traces.
Think of them like a car's dashboard, black box, and GPS combined. Each serves a different purpose, but together they tell the full story.
Logs — The Black Box Recorder
- What it is: Logs are detailed text records of what happened in your system at a given moment.
- Example:
2025-08-04 11:30:45 INFO OrderService: Order 123 created successfully2025-08-04 11:30:45 INFO OrderService: Order 123 created successfully- Analogy: Like the black box in an airplane that records everything happening inside the cockpit.
- Where it helps:
— Debugging a single event or error
— Forensics after a crash
— Tracking user actions
Downside: Logs are great for deep dive into a specific problem, but reading thousands of logs is overwhelming. most of the logs are some mundane information.
Metrics — The Car Dashboard
- What it is: Metrics are numerical measurements over time, like CPU usage, request latency, or error rate.
- Example:
CPU = 75%Order API latency = 120ms- Analogy: Like your car dashboard showing speed, fuel level, and engine temperature.
- Where it helps:
— Real-time monitoring
— Trend analysis (is latency increasing?)
— Setting alerts (e.g., "Alert me if error rate > 5%")
Downside: Metrics tell you something is wrong, but not why.
Traces — The GPS Tracker for a Request
- What it is: A trace shows the entire journey of a request across multiple services, breaking it into spans (smaller steps).
- Example:
— User clicks "Buy" → API Gateway → Order Service → Payment Service → Inventory Service
— Trace shows time spent at each step.
- Analogy: Like a GPS tracker for your food delivery order — it shows every stop and how long it waited there.
- Where it helps:
— Finding where latency comes from
— Identifying which service failed in a chain
— Correlating issues across microservices
Downside: Traces are expensive to store for every request, so usually sampled.
How They Work Together (The Full Picture)
In observability:
- Metrics show: "Something is wrong with Order API — latency is 5x higher."
- Logs show: "Order ID 123 failed because Payment API threw NullPointerException."
- Trace shows: "90% of time spent in Payment Service due to DB slowdown."
Together, they make your system observable, debuggable, and reliable.
Summary
With OpenTelemetry, you can make your Spring Boot microservices observable and debuggable — without locking into a vendor. By leveraging auto-instrumentation, span context propagation, and visualization tools like Jaeger, you can detect bottlenecks, trace errors across services, and build resilient distributed systems.