OpenTelemetry Java Agent for Spring Boot: Complete Setup Guide

Alexandr Bandurchin
November 13, 2025
8 min read

The OpenTelemetry Java Agent provides zero-code instrumentation for Spring Boot applications through bytecode manipulation. This guide covers setup, configuration, auto-instrumentation capabilities, and production deployment strategies for implementing distributed tracing and observability.

What is OpenTelemetry Java Agent?

The OpenTelemetry Java Agent is a JAR file that attaches to any Java 8+ application and automatically instruments code without manual changes. It uses bytecode manipulation to inject telemetry collection at runtime, capturing traces, metrics, and logs from popular frameworks and libraries.

How it works:

  1. Java Agent loads before your application starts
  2. Bytecode manipulation rewrites classes as they load
  3. Instrumentation automatically tracks HTTP requests, database calls, and external services
  4. Telemetry data exports to your observability backend via OTLP

The agent supports 150+ libraries out of the box, including Spring Framework, JDBC drivers, HTTP clients, and message queues.

Quick Start: 5-Minute Setup

Step 1: Download the Agent

bash
# Download latest stable version (2.20.1)
curl -L -O https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

# Verify download
ls -lh opentelemetry-javaagent.jar

Step 2: Configure Environment Variables

bash
export OTEL_SERVICE_NAME="spring-app"
export OTEL_SERVICE_VERSION="1.0.0"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"

Configuration notes:

  • Default OTLP endpoint: http://localhost:4318
  • Default protocol: http/protobuf (changed from grpc in version 2.0+)
  • Service name is required for proper trace identification

Step 3: Run Your Spring Boot Application

bash
java -javaagent:opentelemetry-javaagent.jar \
     -jar target/myapp.jar

Alternative using JAVA_TOOL_OPTIONS:

bash
export JAVA_TOOL_OPTIONS="-javaagent:./opentelemetry-javaagent.jar"
java -jar target/myapp.jar

Verification

Check console output for successful initialization:

text
[otel.javaagent] OpenTelemetry Javaagent 2.20.1 started
[otel.javaagent] Using OTLP endpoint: http://localhost:4318

Test instrumentation by making HTTP requests to your application. Traces should appear in your observability backend within seconds.

Spring Boot Auto-Instrumentation

The Java Agent automatically instruments Spring Boot components without code changes. It intercepts method calls at the bytecode level and wraps them with tracing logic, capturing request details, timing, and errors.

HTTP Layer

The HTTP layer handles incoming requests and outgoing HTTP calls. The agent automatically creates spans for each HTTP transaction, capturing request/response metadata and propagating trace context across service boundaries.

Spring MVC Controllers

REST controllers are automatically instrumented to capture all incoming HTTP requests. Each endpoint invocation creates a span with HTTP method, path, status code, and timing.

java
@RestController
public class OrderController {
    // Automatically instrumented - no annotations needed
    @GetMapping("/orders/{id}")
    public Order getOrder(@PathVariable Long id) {
        return orderService.findById(id);
    }
}

Captured automatically:

  • HTTP method and path
  • Request/response headers
  • Status codes
  • Request duration
  • Exception details

Spring WebFlux

Reactive endpoints using Spring WebFlux are also automatically instrumented. The agent handles reactive streams correctly, maintaining trace context across async operations.

java
@RestController
public class ReactiveController {
    // Auto-instrumented for reactive endpoints
    @GetMapping("/stream")
    public Flux<Event> streamEvents() {
        return eventService.getEventStream();
    }
}

HTTP Clients

Outbound HTTP calls are automatically traced to provide end-to-end visibility across services. The agent injects trace context headers (W3C Trace Context) into requests, enabling distributed tracing.

RestTemplate

Spring's RestTemplate is instrumented to create child spans for outgoing HTTP requests. Each call includes the target URL, HTTP method, status code, and any errors.

java
@Service
public class PaymentService {
    private final RestTemplate restTemplate;

    public PaymentResponse processPayment(Payment payment) {
        // Outbound HTTP call automatically traced
        return restTemplate.postForObject(
            "https://payment-gateway/api/charge",
            payment,
            PaymentResponse.class
        );
    }
}

WebClient (Reactive)

WebClient for reactive applications is fully instrumented, maintaining trace context through reactive chains.

java
@Service
public class UserService {
    private final WebClient webClient;

    public Mono<User> getUser(String id) {
        // Reactive HTTP client auto-instrumented
        return webClient.get()
            .uri("/users/{id}", id)
            .retrieve()
            .bodyToMono(User.class);
    }
}

Database Access

Database operations are automatically instrumented at the JDBC level, capturing SQL queries, connection details, and execution time. Query parameters are sanitized to prevent sensitive data leakage.

Spring Data JPA

Spring Data repositories are instrumented through JDBC. Each database query creates a span containing the SQL statement (with parameters sanitized), database name, and execution duration.

java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // JDBC queries automatically traced with:
    // - SQL statement (sanitized)
    // - Database connection info
    // - Query duration
    List<User> findByStatus(String status);
}

JDBC Template

Direct JDBC operations using JdbcTemplate are fully instrumented, providing visibility into custom queries and batch operations.

java
@Repository
public class OrderRepository {
    private final JdbcTemplate jdbcTemplate;

    public List<Order> findOrders(String customerId) {
        // JDBC operations auto-traced
        return jdbcTemplate.query(
            "SELECT * FROM orders WHERE customer_id = ?",
            new Object[]{customerId},
            orderRowMapper
        );
    }
}

Supported databases:

  • PostgreSQL, MySQL, MariaDB
  • Oracle, SQL Server
  • MongoDB, Redis, Cassandra
  • H2, HSQLDB (test databases)

Messaging Systems

Message queue operations are instrumented to track message production and consumption. The agent propagates trace context through message headers, enabling distributed tracing across async boundaries.

Spring Kafka

Kafka producers and consumers are automatically instrumented. Producer spans capture message send operations, while consumer spans track message processing, including any errors or retries.

java
@Service
public class EventProducer {
    private final KafkaTemplate<String, Event> kafkaTemplate;

    public void sendEvent(Event event) {
        // Kafka produce operations traced
        // Span includes: topic, partition, timestamp
        kafkaTemplate.send("events", event);
    }
}

@KafkaListener(topics = "events")
public void handleEvent(Event event) {
    // Kafka consume operations traced
    // Maintains trace context from producer
    processEvent(event);
}

RabbitMQ

RabbitMQ operations using Spring AMQP are fully instrumented, tracking message publishing and consumption with exchange and routing key details.

java
@Service
public class MessagePublisher {
    private final RabbitTemplate rabbitTemplate;

    public void publish(Message message) {
        // RabbitMQ operations automatically traced
        // Includes: exchange, routing key, message size
        rabbitTemplate.convertAndSend("exchange", "routing.key", message);
    }
}

Complete Instrumentation List

CategorySupported Libraries
Web FrameworksSpring MVC, Spring WebFlux, Servlet API
HTTP ClientsRestTemplate, WebClient, OkHttp, Apache HttpClient
DatabasesJDBC, R2DBC, Hibernate, Spring Data JPA
NoSQLRedis, MongoDB, Cassandra, Elasticsearch
MessagingKafka, RabbitMQ, JMS, AWS SQS
RPCgRPC, Apache Thrift
SchedulingSpring @Scheduled, Quartz
AsyncCompletableFuture, Executor frameworks

View the complete supported libraries list.

Advanced Configuration

Environment Variables Reference

Common configuration options:

bash
# Service identification
export OTEL_SERVICE_NAME="payment-service"
export OTEL_SERVICE_VERSION="2.1.0"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.namespace=payments"

# Export configuration
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otel-collector:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_TIMEOUT="10000"
export OTEL_EXPORTER_OTLP_COMPRESSION="gzip"

# Authentication (if required)
export OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer YOUR_TOKEN"

# Sampling configuration
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"  # Sample 10% of traces

# Batch processing
export OTEL_BSP_SCHEDULE_DELAY="5000"
export OTEL_BSP_MAX_QUEUE_SIZE="2048"
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE="512"

System Properties Alternative

Use -D flags instead of environment variables:

bash
java -javaagent:opentelemetry-javaagent.jar \
     -Dotel.service.name=payment-service \
     -Dotel.traces.exporter=otlp \
     -Dotel.exporter.otlp.endpoint=http://localhost:4318 \
     -Dotel.exporter.otlp.protocol=http/protobuf \
     -jar target/myapp.jar

Priority order (highest to lowest):

  1. System properties (-D flags)
  2. Environment variables
  3. Configuration file
  4. Default values

Configuration File

For complex setups, use a properties file:

properties
# otel-config.properties
otel.service.name=payment-service
otel.service.version=2.1.0
otel.traces.exporter=otlp
otel.metrics.exporter=otlp
otel.exporter.otlp.endpoint=http://localhost:4318
otel.exporter.otlp.protocol=http/protobuf

# Disable specific instrumentations
otel.instrumentation.spring-webmvc.enabled=true
otel.instrumentation.jdbc.enabled=true
otel.instrumentation.kafka.enabled=false

Load the configuration:

bash
java -javaagent:opentelemetry-javaagent.jar \
     -Dotel.javaagent.configuration-file=otel-config.properties \
     -jar target/myapp.jar

Selective Instrumentation

Disable specific instrumentations to reduce overhead:

bash
# Disable all instrumentations except specific ones
export OTEL_INSTRUMENTATION_COMMON_DEFAULT_ENABLED="false"
export OTEL_INSTRUMENTATION_SPRING_WEBMVC_ENABLED="true"
export OTEL_INSTRUMENTATION_JDBC_ENABLED="true"
export OTEL_INSTRUMENTATION_KAFKA_ENABLED="true"

# Or disable specific instrumentations
export OTEL_INSTRUMENTATION_COMMON_DEFAULT_ENABLED="true"
export OTEL_INSTRUMENTATION_LOGBACK_APPENDER_ENABLED="false"
export OTEL_INSTRUMENTATION_SPRING_SCHEDULING_ENABLED="false"

Java Agent vs Spring Boot Starter

Choose the right instrumentation approach for your use case:

FeatureJava AgentSpring Boot Starter
Setup ComplexityMinimal (JAR + env vars)Moderate (dependencies + config)
Code ChangesNone requiredMinimal (configuration)
Auto-Instrumentation150+ librariesLimited to common cases
Startup Overhead100-300ms50-100ms
Memory Footprint+50-100MB+20-40MB
Native Image Support❌ Not supported✅ Supported
ConfigurationEnv vars / system propsSpring application.yml
Custom InstrumentationVia extensionsVia Spring beans
CompatibilityAny Java 8+ appSpring Boot 2.6+ / 3.1+
Agent ConflictsMay conflict with APM agentsNo conflicts
Best ForQuick POC, existing appsSpring Boot 3, Native image, Fine-grained control

Recommendation:

  • Use Java Agent for quickest setup and maximum auto-instrumentation
  • Use Spring Boot Starter for Native Image apps or when you need Spring-native configuration

For detailed Spring Boot Starter setup and custom instrumentation patterns, see Spring Boot OpenTelemetry Integration. For manual instrumentation examples, see OpenTelemetry Java Manual Instrumentation. To compare with Spring's traditional metrics approach, see OpenTelemetry vs Micrometer.

Complete Working Example

Here's a minimal Spring Boot application demonstrating automatic instrumentation. The example shows a REST controller calling a service that performs a database query.

Step 1: Create Spring Boot Project

Use Spring Initializr or create a simple Maven project:

xml
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Step 2: Create a Simple REST Controller

This controller handles incoming HTTP requests. The Java Agent will automatically create a span for each request.

java
package com.example.demo;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // HTTP request span created automatically
        return userService.findUser(id);
    }
}

Step 3: Create a Service Layer

The service performs business logic and database access. Each database call will be traced automatically.

java
package com.example.demo;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findUser(Long id) {
        // Database query traced automatically via JDBC
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

Step 4: Create a JPA Repository

Standard Spring Data JPA repository - no changes needed for instrumentation.

java
package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    // JDBC operations automatically traced
}

Step 5: Run with Java Agent

Download the agent and run your application:

bash
# Download agent
curl -L -O https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.20.1/opentelemetry-javaagent.jar

# Set configuration
export OTEL_SERVICE_NAME="demo-app"
export OTEL_TRACES_EXPORTER="logging"  # Logs traces to console for testing

# Run application with agent
java -javaagent:./opentelemetry-javaagent.jar \
     -jar target/demo-0.0.1-SNAPSHOT.jar

Step 6: Test and Observe

Make a request to your endpoint:

bash
curl http://localhost:8080/api/users/1

What gets traced automatically:

  1. HTTP Request Span - Captures incoming GET request to /api/users/1
  2. Service Method Execution - Times the findUser() method call
  3. Database Query Span - Captures the JDBC SELECT statement with execution time

Console output (with OTEL_TRACES_EXPORTER=logging):

text
[otel.javaagent] Span: GET /api/users/{id}
  - http.method: GET
  - http.route: /api/users/{id}
  - http.status_code: 200

  Child Span: SELECT demo.User
    - db.system: h2
    - db.statement: SELECT * FROM users WHERE id=?
    - db.operation: SELECT

This example demonstrates zero-code instrumentation - no OpenTelemetry imports or annotations in your code, yet you get complete distributed tracing.

For production use, change OTEL_TRACES_EXPORTER to otlp and point to your observability backend.

Key Takeaways

✓    OpenTelemetry Java Agent provides zero-code instrumentation through bytecode manipulation at runtime.

✓    Automatically instruments 150+ libraries including Spring Framework, JDBC, HTTP clients, and messaging systems.

✓    Configuration via environment variables, system properties, or configuration files with clear priority order.

✓    Production deployment requires careful tuning of sampling, batch processing, and resource allocation.

✓    Choose Java Agent for quick setup and maximum coverage; use Spring Boot Starter for Native Image support.

See Also