OpenTelemetry for Spring Boot: Guide with Examples

Vladimir Mihailenco
August 30, 2025
5 min read

Monitoring distributed Spring Boot applications requires robust observability tools. This guide shows you how to implement OpenTelemetry for Spring Boot integration, covering automatic instrumentation, OpenTelemetry manual instrumentation Java examples, and production deployment strategies.

Components of Spring OpenTelemetry

Spring OpenTelemetry provides comprehensive observability through three key data types:

  • Distributed Traces: Complete request flows across microservices
  • Application Metrics: Performance indicators and business KPIs
  • Structured Logs: Contextual events with trace correlation

OpenTelemetry vs Micrometer in Spring

FeatureOpenTelemetryMicrometer
ScopeTraces, Metrics, LogsPrimarily Metrics
Distributed TracingFull W3C standard supportLimited capabilities
Backend SupportAny OTLP-compatible systemVendor-specific
Spring IntegrationAuto + Manual instrumentationNative Spring Boot

Recommendation: Use OpenTelemetry Spring Boot for distributed systems requiring comprehensive observability. For APM capabilities, OpenTelemetry provides full application performance monitoring with traces, metrics, and logs. For a detailed comparison, see OpenTelemetry vs Micrometer.

Business Benefits of Spring

Implementing OpenTelemetry in Spring Boot applications delivers measurable improvements:

  • Reduced MTTR: Faster root cause identification with distributed traces
  • Proactive Monitoring: Detect issues before user impact
  • End-to-End Visibility: Track requests across entire microservice architecture
  • Performance Optimization: Data-driven optimization decisions

Organizations typically see 40-60% reduction in incident resolution time after proper implementation.

Step-by-Step Spring Implementation

Best for: Quick setup with zero code changes

For a complete guide to the Java Agent with Spring Boot, see OpenTelemetry Java Agent for Spring Boot. For other Java instrumentation options, see the OpenTelemetry Java guide.

bash
# Download OpenTelemetry Java Agent
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

# Configure environment
export OTEL_SERVICE_NAME=spring-app
export OTEL_SERVICE_VERSION=1.0.0
export OTEL_TRACES_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.uptrace.dev:4317
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=<your_uptrace_dsn>

# Run with instrumentation
java -javaagent:opentelemetry-javaagent.jar -jar your-spring-app.jar

Approach 2: Spring Boot Starter

Best for: Custom instrumentation and fine-grained control

Dependencies (Gradle):

gradle
dependencies {
    implementation 'io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter:1.32.0-alpha'
    implementation 'io.opentelemetry:opentelemetry-exporter-otlp:1.32.0'
}

Configuration (application.yml):

Add these properties to configure OpenTelemetry through Spring Boot's configuration system:

yaml
otel:
  service:
    name: ${spring.application.name}
  traces:
    exporter: otlp
    sampler: parentbased_traceidratio
    sampler.arg: 1.0
  exporter:
    otlp:
      endpoint: https://api.uptrace.dev:4317
      headers: uptrace-dsn=<your_uptrace_dsn>

Spring Boot Example

Let's implement a complete working example:

bash
# Clone demo application
git clone https://github.com/gothinkster/spring-boot-realworld-example-app.git
cd spring-boot-realworld-example-app

# Build application
./gradlew bootJar

# Run with OpenTelemetry
java -javaagent:opentelemetry-javaagent.jar \
     -jar build/libs/spring-boot-realworld-example-app-0.0.1-SNAPSHOT.jar

# Test API
curl http://localhost:8080/tags

Custom Instrumentation for Business Telemetry

For comprehensive manual instrumentation patterns with complete working examples, see OpenTelemetry Java Manual Instrumentation.

Custom Trace Implementation

This example shows how to create custom spans that capture business-specific context and operations:

java
@Service
public class OrderProcessingService {

    private final Tracer tracer;

    public OrderProcessingService(OpenTelemetry openTelemetry) {
        this.tracer = openTelemetry.getTracer("com.example.orders", "1.0.0");
    }

    public OrderResult processOrder(OrderRequest request) {
        Span orderSpan = tracer.spanBuilder("process-order")
            .setAttribute("order.id", request.getOrderId())
            .setAttribute("customer.id", request.getCustomerId())
            .setAttribute("order.amount", request.getTotalAmount())
            .startSpan();

        try (Scope scope = orderSpan.makeCurrent()) {
            // Business logic with automatic context propagation
            validateOrder(request);
            orderSpan.addEvent("order.validated");

            PaymentResult payment = processPayment(request);
            orderSpan.setAttribute("payment.status", payment.getStatus());

            OrderResult result = finalizeOrder(request);
            orderSpan.setStatus(StatusCode.OK);

            return result;
        } catch (Exception e) {
            orderSpan.setStatus(StatusCode.ERROR, e.getMessage());
            orderSpan.recordException(e);
            throw e;
        } finally {
            orderSpan.end();
        }
    }
}

Business Metrics Collection

Use this pattern to capture custom business metrics that provide insights into your application's performance and user behavior:

java
@Component
public class BusinessMetricsCollector {

    private final LongCounter orderCounter;
    private final DoubleHistogram orderValueHistogram;

    public BusinessMetricsCollector(OpenTelemetry openTelemetry) {
        Meter meter = openTelemetry.getMeter("com.example.business", "1.0.0");

        orderCounter = meter.counterBuilder("orders.processed.total")
            .setDescription("Total orders processed")
            .build();

        orderValueHistogram = meter.histogramBuilder("order.value")
            .setDescription("Order value distribution")
            .setUnit("USD")
            .build();
    }

    public void recordOrder(String type, String status, double value) {
        orderCounter.add(1, Attributes.of(
            AttributeKey.stringKey("order.type"), type,
            AttributeKey.stringKey("order.status"), status
        ));

        orderValueHistogram.record(value, Attributes.of(
            AttributeKey.stringKey("order.type"), type
        ));
    }
}

Enhanced Controller Instrumentation

Here's how to enrich your REST controllers with additional telemetry context and business metrics:

java
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderProcessingService orderService;
    private final BusinessMetricsCollector metricsCollector;

    @PostMapping
    public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
        // Current span enrichment
        Span currentSpan = Span.current();
        currentSpan.setAttribute("order.items.count", request.getItems().size());
        currentSpan.setAttribute("customer.region", request.getCustomerRegion());

        try {
            OrderResult result = orderService.processOrder(request);

            // Record business metrics
            metricsCollector.recordOrder(
                request.getOrderType(),
                result.getStatus(),
                request.getTotalAmount()
            );

            currentSpan.setStatus(StatusCode.OK);
            return ResponseEntity.ok(new OrderResponse(result.getOrderId()));

        } catch (OrderValidationException e) {
            currentSpan.setStatus(StatusCode.ERROR, "Validation failed");
            return ResponseEntity.badRequest()
                .body(new ErrorResponse("VALIDATION_ERROR", e.getMessage()));
        }
    }
}

Otel Collector Setup

Basic Configuration

Here's a minimal collector configuration that receives telemetry from your Spring Boot app and forwards it to your observability backend:

yaml
# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 5s
    send_batch_size: 1000
  memory_limiter:
    limit_mib: 1000

exporters:
  otlp:
    endpoint: https://your-backend:4317
    headers:
      authorization: Bearer ${API_TOKEN}

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp]

Production Deployment

This Docker Compose setup demonstrates how to deploy your Spring Boot app with the collector in a production-like environment:

yaml
# docker-compose.yml
version: '3.8'
services:
  spring-app:
    image: your-spring-app:latest
    environment:
      - OTEL_SERVICE_NAME=spring-app
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
      - OTEL_TRACES_SAMPLER=parentbased_traceidratio
      - OTEL_TRACES_SAMPLER_ARG=0.1
    depends_on:
      - otel-collector

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"

Troubleshooting

No Telemetry Data

Symptoms: Application runs but no traces appear in backend

Diagnosis:

bash
# Enable debug logging
export OTEL_JAVAAGENT_DEBUG=true

# Check connectivity
curl -v http://your-collector:4317/v1/traces

Solution:

yaml
otel:
  exporter:
    otlp:
      endpoint: http://collector:4317  # Correct endpoint
      protocol: grpc                   # Match collector config

Broken Trace Propagation

Symptoms: Disconnected spans across services

Diagnosis:

java
@GetMapping("/debug/trace")
public Map<String, String> debugTrace(HttpServletRequest request) {
    Map<String, String> headers = new HashMap<>();
    headers.put("traceparent", request.getHeader("traceparent"));
    headers.put("current-trace-id", Span.current().getSpanContext().getTraceId());
    return headers;
}

Solution:

java
@Bean
public TextMapPropagator textMapPropagator() {
    return TextMapPropagator.composite(
        W3CTraceContextPropagator.getInstance(),
        W3CBaggagePropagator.getInstance()
    );
}

High Memory Usage

Symptoms: Increasing heap usage, frequent GC

Solutions:

yaml
# Optimize batch processing
otel:
  bsp:
    max.queue.size: 1024
    max.export.batch.size: 256
    schedule.delay: 2s

# Implement sampling
otel:
  traces:
    sampler: parentbased_traceidratio
    sampler.arg: 0.1  # 10% sampling

Performance and Resource Impact

Performance Impact Analysis

ComponentOverheadMitigation
CPU Usage3-8%Optimize sampling rates
Memory10-15%Configure buffer limits
Network5-10KB/requestEnable compression
Latency1-5ms/requestUse async exporters

Production Optimization

These environment variables help optimize OpenTelemetry performance for high-traffic production environments:

bash
# JVM optimization
export JAVA_OPTS="-XX:+UseG1GC -Xms2g -Xmx4g"

# OpenTelemetry performance tuning
export OTEL_BSP_SCHEDULE_DELAY=5000
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512
export OTEL_TRACES_SAMPLER_ARG=0.1

# Disable unnecessary instrumentation
export OTEL_INSTRUMENTATION_LOGBACK_APPENDER_ENABLED=false

Best Practices

Environment-Specific Configurations

Development:

yaml
otel:
  traces:
    sampler: parentbased_always_on  # Full sampling
  exporter:
    otlp:
      endpoint: http://localhost:4317

Production:

yaml
otel:
  traces:
    sampler: parentbased_traceidratio
    sampler.arg: 0.1  # Controlled sampling
  exporter:
    otlp:
      endpoint: https://prod-collector:4317
      compression: gzip

Monitoring Strategy

  1. Start Simple: Begin with Java Agent for immediate results
  2. Add Context: Implement custom instrumentation for business insights
  3. Optimize Gradually: Fine-tune sampling and performance settings
  4. Monitor Impact: Track OpenTelemetry's resource usage
  5. Scale Appropriately: Adjust configuration based on traffic patterns

Conclusion

OpenTelemetry in Spring Boot implementation provides comprehensive observability with manageable complexity. Key success factors:

  • Quick Start: Java Agent enables immediate instrumentation
  • Business Context: Custom metrics provide actionable insights
  • Production Ready: Proper sampling and resource management
  • Incremental Adoption: Gradual rollout across services

With proper Spring Boot OpenTelemetry setup, you achieve end-to-end visibility while maintaining application performance.

For servlet container monitoring, see Tomcat instrumentation to monitor the underlying server infrastructure.

Ready to start? Sign up for Uptrace and begin monitoring your Spring Boot applications with comprehensive observability today! Compare with other top APM tools or explore open-source APM deployment options.

Additional Resources