OpenTelemetry Java Agent for Spring Boot: Complete Setup Guide
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:
- Java Agent loads before your application starts
- Bytecode manipulation rewrites classes as they load
- Instrumentation automatically tracks HTTP requests, database calls, and external services
- 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
# 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
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 fromgrpcin version 2.0+) - Service name is required for proper trace identification
Step 3: Run Your Spring Boot Application
java -javaagent:opentelemetry-javaagent.jar \
-jar target/myapp.jar
Alternative using JAVA_TOOL_OPTIONS:
export JAVA_TOOL_OPTIONS="-javaagent:./opentelemetry-javaagent.jar"
java -jar target/myapp.jar
Verification
Check console output for successful initialization:
[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.
@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.
@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.
@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.
@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.
@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.
@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.
@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.
@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
| Category | Supported Libraries |
|---|---|
| Web Frameworks | Spring MVC, Spring WebFlux, Servlet API |
| HTTP Clients | RestTemplate, WebClient, OkHttp, Apache HttpClient |
| Databases | JDBC, R2DBC, Hibernate, Spring Data JPA |
| NoSQL | Redis, MongoDB, Cassandra, Elasticsearch |
| Messaging | Kafka, RabbitMQ, JMS, AWS SQS |
| RPC | gRPC, Apache Thrift |
| Scheduling | Spring @Scheduled, Quartz |
| Async | CompletableFuture, Executor frameworks |
View the complete supported libraries list.
Advanced Configuration
Environment Variables Reference
Common configuration options:
# 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:
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):
- System properties (
-Dflags) - Environment variables
- Configuration file
- Default values
Configuration File
For complex setups, use a properties file:
# 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:
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.javaagent.configuration-file=otel-config.properties \
-jar target/myapp.jar
Selective Instrumentation
Disable specific instrumentations to reduce overhead:
# 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:
| Feature | Java Agent | Spring Boot Starter |
|---|---|---|
| Setup Complexity | Minimal (JAR + env vars) | Moderate (dependencies + config) |
| Code Changes | None required | Minimal (configuration) |
| Auto-Instrumentation | 150+ libraries | Limited to common cases |
| Startup Overhead | 100-300ms | 50-100ms |
| Memory Footprint | +50-100MB | +20-40MB |
| Native Image Support | ❌ Not supported | ✅ Supported |
| Configuration | Env vars / system props | Spring application.yml |
| Custom Instrumentation | Via extensions | Via Spring beans |
| Compatibility | Any Java 8+ app | Spring Boot 2.6+ / 3.1+ |
| Agent Conflicts | May conflict with APM agents | No conflicts |
| Best For | Quick POC, existing apps | Spring 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:
<!-- 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.
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.
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.
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:
# 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:
curl http://localhost:8080/api/users/1
What gets traced automatically:
- HTTP Request Span - Captures incoming GET request to
/api/users/1 - Service Method Execution - Times the
findUser()method call - Database Query Span - Captures the JDBC
SELECTstatement with execution time
Console output (with OTEL_TRACES_EXPORTER=logging):
[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.