Spring Boot
Spring Boot is one of the most widely adopted Java frameworks for building enterprise microservices and web applications. However, understanding application performance, identifying bottlenecks, and troubleshooting issues in distributed Spring Boot environments can be challenging without proper observability. OpenTelemetry provides automatic instrumentation for Spring Boot applications, capturing distributed traces across HTTP requests, database calls, message queues, and external API interactions. With base14 Scout's OpenTelemetry integration, you gain complete visibility into your Spring Boot application's performance with minimal code changes and production-ready configuration.
Spring Boot is the dominant JVM framework. Quarkus and Micronaut are compile-time-optimized alternatives, and Ktor is the Kotlin-first option.
This comprehensive guide demonstrates how to instrument Spring Boot applications using OpenTelemetry, covering everything from basic setup to advanced production scenarios. You'll learn how to automatically capture traces from Spring MVC controllers, Spring Data JPA repositories, RestTemplate and WebClient calls, Kafka consumers, and more. The OpenTelemetry Spring Boot starter provides zero-code instrumentation for most common libraries, while also offering APIs for custom instrumentation when you need fine-grained control over spans and attributes. Integration with Spring Boot Actuator enables health checks and metrics export, making it production-ready from day one.
Whether you're a Java developer adding observability to a new microservice, a DevOps engineer standardizing APM across Spring Boot services, or a platform team implementing OpenTelemetry organization-wide, this guide provides practical examples for every scenario. You'll find solutions for common pain points like context propagation across async boundaries, instrumenting legacy Spring applications, securing sensitive data in traces, optimizing performance overhead, and troubleshooting missing spans. By the end of this guide, you'll have a fully instrumented Spring Boot application sending rich telemetry data to base14 Scout, enabling fast root cause analysis and proactive performance optimization.
Add the opentelemetry-spring-boot-starter dependency to your pom.xml or
build.gradle and set otel.exporter.otlp.endpoint in
application.properties - the starter auto-instruments Spring MVC, Spring
Data JPA, RestTemplate, WebClient, and Kafka with zero code changes. For
fine-grained control over specific operations, inject Tracer via the
OpenTelemetry API and use startActiveSpan to add custom spans alongside the
automatic instrumentation.
Storing and querying this data at production volume is what base14 Scout does. Check out Scout APM.
Overview
This guide demonstrates how to:
- Set up OpenTelemetry instrumentation for Spring Boot applications
- Configure automatic request and response tracing
- Instrument Spring MVC, Spring Data, and other Spring components
- Implement custom instrumentation for business logic
- Collect traces, metrics, and logs
- Secure sensitive data in telemetry
- Optimize performance overhead in production
- Troubleshoot common instrumentation issues
- Export telemetry data to base14 Scout Collector
Who This Guide Is For
This guide is designed for multiple roles working with Spring Boot applications:
-
Java Developers building new Spring Boot microservices who want to add observability from the start, understand performance characteristics of their code, and debug issues faster with distributed tracing.
-
DevOps Engineers responsible for deploying and monitoring Spring Boot applications in production environments, who need to standardize APM tooling, configure exporters, and ensure observability across the entire infrastructure.
-
Platform Teams implementing organization-wide OpenTelemetry standards for Java applications, who need to create reusable configuration patterns and integrate with existing observability stacks.
-
Site Reliability Engineers (SREs) who troubleshoot production incidents involving Spring Boot services, need to quickly identify performance bottlenecks, trace requests across distributed systems, and maintain service level objectives (SLOs).
-
Application Architects making technology decisions for microservices platforms, evaluating observability solutions, and designing systems that are observable by default with proper instrumentation patterns.
Prerequisites
Before implementing OpenTelemetry instrumentation, ensure you have:
- A Spring Boot application (2.6+, 3.1+ or 4.x)
- Java Development Kit (JDK) installed
- Maven 3.6+ or Gradle 7.6+ build tool
- Access to base14 Scout Collector endpoint (see Collector Setup)
- Basic understanding of OpenTelemetry concepts (spans, traces, exporters)
Compatibility
Minimum requirements:
- Java: 8+ for Spring Boot 2.x, 17+ for Spring Boot 3 and 4 (Java 25 LTS is supported)
- Spring Boot: 2.6+, 3.1+ or 4.x. Spring Boot 4 needs starter 2.23.0 or later
- Maven/Gradle: Any recent version
Version details:
OpenTelemetry library versions
| Component | Minimum | Recommended |
|---|---|---|
| OpenTelemetry Java SDK | 1.32.0 | 1.67+ |
| OpenTelemetry Spring Boot Starter | 2.1.0 | 2.33.0 |
| OpenTelemetry Java Agent | 1.32.0 | 2.33+ |
Java 25
Java 25 is an LTS release and works with both the starter and the agent. The spring-boot-java25-postgresql example runs on Java 25 with Spring Boot 4.1.2 and starter 2.33.0.
Choosing Your Approach
Spring Boot offers three distinct approaches for OpenTelemetry integration. This section helps you choose the right one for your project and understand the trade-offs.
📌 Looking for alternative approaches?
This guide focuses on the OpenTelemetry SDK Integration (recommended for production). For Java Agent (zero-code) and Spring Boot 4 Native Starter approaches, see Spring Boot Alternatives.
OpenTelemetry SDK Integration (Our Recommendation) ⭐
The community-maintained opentelemetry-spring-boot-starter provides direct
OpenTelemetry SDK integration with full control and broad version support. This
is our recommended approach for production deployments.
How it works:
- Add Maven/Gradle dependencies to your
pom.xmlorbuild.gradle - Configure via
application.ymlorapplication.properties - Automatic instrumentation for ~30 common libraries
- Full OpenTelemetry API access for custom spans and metrics
- Works with Spring Boot 2.6+, 3.1+ and 4.x
Architecture:
- Direct OpenTelemetry API usage (no abstraction layer)
- Full access to OpenTelemetry SDK configuration
- Explicit dependency management via BOM
- Spring Boot native configuration support
- Production-proven across thousands of deployments
Best for:
- Production deployments - Stable, battle-tested approach
- GraalVM native-image - Full support for native compilation
- Spring Boot configuration - Use familiar
application.ymlpatterns - Custom instrumentation - Full OpenTelemetry API access for business logic
- Advanced use cases - Custom span processors and exporters
- Multi-framework - Using OpenTelemetry across Node.js, Go, Python, etc.
- Version control - Explicit dependency management without bytecode issues
Limitations:
- More complex setup than Java Agent (requires BOM and dependencies)
- Requires understanding OpenTelemetry concepts (spans, traces, exporters)
- Fewer auto-instrumented libraries (30+) compared to Java Agent (150+)
- Manual configuration needed for advanced scenarios
Java Agent (Zero-Code Alternative)
The OpenTelemetry Java agent provides zero-code automatic instrumentation using bytecode manipulation at JVM startup. Best for legacy applications where code changes aren't possible, or for quick proof-of-concept testing.
Key characteristics:
- Download single JAR, attach with
-javaagentflag - No dependencies or code changes required
- Automatically instruments 150+ libraries
- Environment variable configuration only
See the Java Agent guide for complete setup instructions, Docker/Kubernetes examples, and limitations.
Spring Boot 4 Native Starter
Spring Boot 4 ships its own spring-boot-starter-opentelemetry. It exports
Micrometer observations over OTLP and is configured with management.*
properties instead of otel.*.
Key characteristics:
- Single dependency, managed by Spring Boot 4
- Micrometer Observation and Tracing APIs for custom instrumentation
- Configured with
management.opentelemetry.*andmanagement.otlp.metrics.export.*properties - Application logs are not exported over OTLP without extra setup
See the Spring Boot 4 guide for setup instructions and migration guide from OpenTelemetry SDK Integration.
Quick Comparison
| Feature | OpenTelemetry SDK Integration ⭐ | Java Agent | Spring Boot 4 Native |
|---|---|---|---|
| Production Ready | ✅ Stable | ✅ Stable | ✅ Stable (Boot 4 only) |
| Setup | Add dependencies | Attach JAR at startup | Add single dependency |
| Code Changes | Optional | None required | Optional |
| Configuration | application.yml or code | Environment variables only | application.yml or code |
| Custom Instrumentation | Full OpenTelemetry API | Annotations only | Micrometer API |
| GraalVM Native | ✅ Supported | ❌ Not supported | ✅ Supported |
| Auto-instrumentation | ~30 libraries | 150+ libraries | Via Micrometer |
| Best For | Production apps, full control | Legacy apps, zero-code | Micrometer-first teams |
| Our Recommendation | ✅ Recommended | Alternative | Alternative |
Decision Guide
Choose OpenTelemetry SDK Integration (Recommended for Production) when:
- GraalVM native-image: Compiling to native binary
- Spring Boot configuration: Want to use
application.ymlfor OTEL config - Custom instrumentation: Need full OpenTelemetry API access for business logic
- Version flexibility: Want explicit control over dependency versions
- Custom span processors: Need advanced SDK customization (processors, exporters)
- Multi-framework: Using OpenTelemetry across Node.js, Go, Python, etc.
- Version range: Spring Boot 2.6+, 3.1+ and 4.x
- Latest features: Want immediate access to new OpenTelemetry capabilities
- Debugging preference: Prefer explicit dependencies over bytecode manipulation
Choose Java Agent (Zero-Code Alternative) when:
- Zero-code requirement: Cannot or don't want to modify application code/dependencies
- Legacy applications: Modifying code is difficult or risky
- Maximum auto-instrumentation: Need coverage for 150+ libraries out-of-the-box
- Cross-stack standardization: Using OpenTelemetry across multiple languages
- Quick evaluation: Want to try OpenTelemetry without code changes
- Operational control: Ops team manages instrumentation separately from development
- Not using GraalVM: No native-image compilation requirements
- Simple Spring Boot apps: Standard libraries, no version compatibility concerns
Choose Spring Boot 4 Native Starter when:
- Spring Boot 4: The application already runs on Spring Boot 4
- Micrometer standardization: Team already uses Micrometer abstractions
- Spring-native conventions: Want Spring Boot auto-configuration patterns
- Simple setup: Don't need advanced SDK customization
- Vendor-neutral API: Micrometer can export to multiple backends
This Guide's Coverage
This guide focuses on the OpenTelemetry SDK Integration approach with comprehensive examples for production use:
- Complete setup with Maven and Gradle
- Configuration via application.yml, properties, and programmatic
- Production deployment with Docker, Kubernetes
- Framework-specific features (Spring MVC, JPA, RestTemplate, WebClient)
- Custom instrumentation with full OpenTelemetry API
- Security, performance, and troubleshooting guidance
Why OpenTelemetry SDK Integration is our recommendation:
- Production Ready: Stable, well-tested, and widely deployed
- Broad Compatibility: Works with Spring Boot 2.6+, 3.1+ and 4.x
- Maximum Flexibility: Full SDK control for complex scenarios
- GraalVM Support: Native-image compilation compatible
- Spring Configuration: Uses familiar
application.ymlpatterns
💡 Alternative Approaches: For Java Agent (zero-code) or Spring Boot 4 Native Starter, see Spring Boot Alternatives.
Setup
- Maven
- Gradle
<properties>
<!-- Overrides the OpenTelemetry version that Spring Boot manages -->
<opentelemetry.version>1.67.0</opentelemetry.version>
<opentelemetry.instrumentation.version>2.33.0</opentelemetry.instrumentation.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- OpenTelemetry Instrumentation BOM -->
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>${opentelemetry.instrumentation.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!-- OpenTelemetry -->
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
</dependencies>
plugins {
id 'java'
id 'org.springframework.boot' version '4.1.2'
id 'io.spring.dependency-management' version '1.1.7'
}
ext {
set('opentelemetry.version', '1.67.0')
set('opentelemetry.instrumentation.version', '2.33.0')
}
dependencyManagement {
imports {
// OpenTelemetry Instrumentation BOM
mavenBom "io.opentelemetry.instrumentation:" +
"opentelemetry-instrumentation-bom:" +
"${opentelemetry.instrumentation.version}"
}
}
dependencies {
// Spring Boot Starters
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
// OpenTelemetry
implementation 'io.opentelemetry.instrumentation:' +
'opentelemetry-spring-boot-starter'
}
On Spring Boot 2.x and 3.x, use spring-boot-starter-web instead of
spring-boot-starter-webmvc. Do not add micrometer-tracing-bridge-otel next
to the starter: on Spring Boot 3.x it creates a second HTTP server span for
each request, such as http get /users/.
Configuration
OpenTelemetry can be configured for Spring Boot applications in multiple ways. Choose the approach that best fits your deployment model.
- Environment Variables
- Application Properties
- Programmatic (Java)
- Spring Profiles (YAML)
Recommended for production - Maximum flexibility across environments:
# Service identification
OTEL_SERVICE_NAME=your-service-name
OTEL_RESOURCE_ATTRIBUTES=service.namespace=your-namespace,environment=development
# OTLP Exporter configuration
OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
# Instrumentation control
OTEL_INSTRUMENTATION_SPRING_WEBMVC_ENABLED=true
OTEL_INSTRUMENTATION_JDBC_ENABLED=true
# Semantic convention stability
OTEL_SEMCONV_STABILITY_OPT_IN=http,database
On the OpenTelemetry Java instrumentation 2.x line, HTTP server spans already
use the stable conventions by default (http.request.method,
http.response.status_code, url.path, http.route), so the http token is
effectively a no-op. The database token is what does the work: with it set,
JDBC spans emit the stable database conventions (db.query.text,
db.system.name, db.namespace, db.query.summary) instead of the legacy
db.statement / db.system. Both tokens are kept here so HTTP and database
telemetry stay on the stable names together.
Spring Boot-native configuration via application.properties:
# Server
server.port=8080
server.address=0.0.0.0
# OpenTelemetry
otel.service.name=your-service-name
otel.resource.attributes=service.namespace=your-namespace,\
environment=dev
# OTLP Exporter
otel.traces.exporter=otlp
otel.metrics.exporter=otlp
otel.logs.exporter=otlp
otel.exporter.otlp.endpoint=http://localhost:4318
otel.exporter.otlp.protocol=http/protobuf
# HTTP semconv is stable by default on 2.x; database opt-in emits db.query.text
otel.semconv-stability.opt-in=http,database
# Actuator
management.endpoints.web.exposure.include=health,info,metrics
Advanced scenarios with full programmatic control:
package com.example.config;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.ServiceAttributes;
import io.opentelemetry.semconv.incubating.DeploymentIncubatingAttributes;
import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenTelemetryConfig {
@Value("${otel.exporter.otlp.endpoint:http://localhost:4318}")
private String otlpEndpoint;
@Value("${otel.service.name:spring-boot-app}")
private String serviceName;
@Bean
public OpenTelemetry openTelemetry() {
Resource resource = Resource.create(
Attributes.of(
ServiceAttributes.SERVICE_NAME, serviceName,
ServiceIncubatingAttributes.SERVICE_NAMESPACE, "production",
DeploymentIncubatingAttributes.DEPLOYMENT_ENVIRONMENT_NAME, "development"
)
);
OtlpHttpSpanExporter spanExporter = OtlpHttpSpanExporter.builder()
.setEndpoint(otlpEndpoint + "/v1/traces")
.build();
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setResource(resource)
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.build();
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.buildAndRegisterGlobal();
}
}
Multi-environment configuration with Spring profiles:
application-dev.yml:
otel:
service:
name: my-service-dev
resource:
attributes: environment=dev,service.namespace=development
exporter:
otlp:
endpoint: http://localhost:4318
application-prod.yml:
otel:
service:
name: my-service-prod
resource:
attributes: environment=demo,service.namespace=production
exporter:
otlp:
endpoint: https://scout-collector.example.com:4318
All configuration approaches export logs, traces, and metrics to the base14 Scout observability backend.
Traces
Traces follow a request through your Spring Boot application, from the Spring MVC controller that receives it, through service beans, JDBC queries, RestTemplate / WebClient calls, and Kafka or RabbitMQ messages, and back out as the response.
Automatic Trace Collection
With the OpenTelemetry Java agent or the Spring Boot starter attached, every request is traced with no per-controller code:
Captured Information:
- HTTP method, route template, and status code for each Spring MVC endpoint
- Request duration and a span-by-span timing breakdown
- JDBC queries (Hibernate, Spring Data JPA, JdbcTemplate), including the SQL
- Outbound
RestTemplate,WebClient, and Feign calls - Kafka, RabbitMQ, and JMS producer/consumer spans
- Exceptions recorded on the failing span with stack traces
- Distributed context propagation across services (W3C Trace Context)
Trace Hierarchy:
HTTP Request Span (root: GET /api/users/{id})
├── UserController.getUser Span
│ ├── UserService.findById Span
│ │ └── JDBC Query Span (SELECT ... FROM users)
│ └── RestTemplate Span (GET inventory-service)
└── Kafka Producer Span (user.viewed event)
Key Tracing Features
- Automatic HTTP tracking: every controller route is traced with no code changes
- JDBC and ORM visibility: Hibernate and Spring Data queries appear as child spans with the executed SQL
- Error capturing: thrown exceptions and
@ExceptionHandlerpaths are recorded with full stack traces - Context propagation: distributed traces follow requests across HTTP and messaging boundaries
- Async support:
@Asyncmethods,CompletableFuture, and reactive WebFlux pipelines stay correctly parented
View traces in your base14 Scout dashboard to follow request flows and find the slow span in a chain.
Reference
Metrics
Metrics aggregate runtime measurements over time, such as request rate, latency distributions, JVM health, and connection-pool saturation. Where traces explain a single request, metrics power dashboards and alerts across all of them.
Automatic Metrics
The OpenTelemetry Java agent or Spring Boot starter captures a broad set of
metrics without code, exported via the OTLP metrics exporter you configured
above (otel.metrics.exporter=otlp):
- JVM runtime: heap and non-heap usage, GC pause time, thread counts, class loading
- HTTP server: request count and
http.server.request.durationper route and status - JDBC / HikariCP: connection-pool size, active and idle connections, wait time
- System: process CPU and memory
Custom Metrics with Micrometer
Spring Boot apps already ship with Micrometer, and the OpenTelemetry Spring
Boot starter can bridge Micrometer's MeterRegistry to OpenTelemetry. The
bridge is off by default. Keep Spring Boot Actuator on the classpath and enable
it with otel.instrumentation.micrometer.enabled=true, then inject
MeterRegistry:
package com.example.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class OrderMetrics {
private final Counter ordersPlaced;
public OrderMetrics(MeterRegistry registry) {
this.ordersPlaced = Counter.builder("orders.placed")
.description("Total number of orders placed")
.register(registry);
}
public void recordOrder() {
ordersPlaced.increment();
}
}
View metrics in your base14 Scout dashboard to chart request rate, latency percentiles, JVM health, and custom business counters.
Reference
Official Metrics Documentation
Production Configuration
Production environments require careful configuration for performance, reliability, and cost optimization.
Batch Span Processor Configuration
Use BatchSpanProcessor for optimal production performance:
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
@Profile("production")
public class ProductionTracingConfig {
@Bean
public BatchSpanProcessor batchSpanProcessor(OtlpHttpSpanExporter exporter) {
return BatchSpanProcessor.builder(exporter)
.setMaxQueueSize(2048)
.setMaxExportBatchSize(512)
.setScheduleDelay(Duration.ofSeconds(5))
.setExporterTimeout(Duration.ofSeconds(30))
.build();
}
}
Resource Attributes for Production
Add comprehensive resource attributes for better observability:
otel.resource.attributes=\
service.name=payment-service,\
service.namespace=production,\
service.version=1.2.3,\
service.instance.id=${HOSTNAME},\
environment=demo,\
deployment.region=us-east-1,\
cloud.provider=aws,\
cloud.platform=aws_eks,\
k8s.cluster.name=prod-cluster,\
k8s.namespace.name=payments,\
k8s.pod.name=${HOSTNAME}
Docker Configuration
Configure OpenTelemetry for containerized Spring Boot applications:
FROM eclipse-temurin:25-jre-jammy
WORKDIR /app
# Copy application JAR
COPY target/my-service-1.0.0.jar app.jar
# Set OpenTelemetry environment variables
ENV OTEL_SERVICE_NAME=my-service
ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318
ENV OTEL_RESOURCE_ATTRIBUTES=service.namespace=production
# Run application
ENTRYPOINT ["java", "-jar", "app.jar"]
Docker Compose Example
version: "3.8"
services:
app:
build: .
ports:
- "8080:8080"
environment:
OTEL_SERVICE_NAME: payment-service
OTEL_EXPORTER_OTLP_ENDPOINT: http://scout-collector:4318
OTEL_RESOURCE_ATTRIBUTES: environment=development
depends_on:
- scout-collector
scout-collector:
image: base14/scout-collector:latest
ports:
- "4318:4318"
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: spring-boot-app
template:
metadata:
labels:
app: spring-boot-app
spec:
containers:
- name: app
image: myregistry/spring-boot-app:1.2.3
ports:
- containerPort: 8080
env:
- name: OTEL_SERVICE_NAME
value: "spring-boot-app"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://scout-collector.observability.svc.cluster.local:4318"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "environment=demo,k8s.cluster.name=prod-cluster"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
Health Check Endpoint
Implement a health check that verifies telemetry export:
package com.example.controller;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HealthController {
@Autowired
private OpenTelemetry openTelemetry;
@GetMapping("/health/telemetry")
public ResponseEntity<String> checkTelemetry() {
try {
Tracer tracer = openTelemetry.getTracer("health-check");
// Test span creation
tracer.spanBuilder("health-check-test").startSpan().end();
return ResponseEntity.ok("Telemetry OK");
} catch (Exception e) {
return ResponseEntity.status(500).body("Telemetry Error: " + e.getMessage());
}
}
}
Framework-Specific Features
OpenTelemetry automatically instruments many Spring Boot components. Here's how instrumentation works for common Spring features.
Spring MVC REST Controllers
REST controllers are automatically instrumented:
package com.example.controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// Automatically creates span: "GET /api/users/{id}"
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// Automatically creates span: "POST /api/users"
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Each HTTP request creates a parent span with attributes like
http.request.method, http.route, http.response.status_code, and
url.full.
Spring Data JPA Repositories
Database queries via Spring Data JPA are automatically traced:
package com.example.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface UserRepository extends JpaRepository<User, Long> {
// Automatically creates span: "SELECT User"
User findByEmail(String email);
// Automatically creates span with SQL query
@Query("SELECT u FROM User u WHERE u.active = true")
List<User> findActiveUsers();
}
JDBC instrumentation captures:
- SQL statements (parameterized)
- Database connection details
- Query execution time
- Connection pool metrics
RestTemplate and WebClient
Outgoing HTTP calls are automatically instrumented with distributed trace context propagation:
package com.example.service;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class ExternalApiService {
private final RestTemplate restTemplate;
private final WebClient webClient;
public ExternalApiService(RestTemplate restTemplate, WebClient.Builder webClientBuilder) {
this.restTemplate = restTemplate;
this.webClient = webClientBuilder.baseUrl("https://api.example.com").build();
}
// Automatically creates span: "GET https://api.example.com/data"
public String fetchDataSync() {
return restTemplate.getForObject("https://api.example.com/data", String.class);
}
// Automatically creates span with async context propagation
public Mono<String> fetchDataAsync() {
return webClient.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class);
}
}
Spring Boot Actuator Integration
Integrate OpenTelemetry metrics with Spring Boot Actuator:
package com.example.config;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ActuatorConfig {
@Bean
public Meter meter(OpenTelemetry openTelemetry) {
return openTelemetry.getMeter("actuator-metrics");
}
}
# Expose Actuator endpoints
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
Custom Instrumentation
While auto-instrumentation covers most use cases, custom instrumentation allows fine-grained control over spans and attributes.
💡 Java Agent Users: Add
io.opentelemetry:opentelemetry-apias a dependency and the code in this section works with the agent too; the agent supplies the implementation at runtime. For@WithSpanannotation examples, see Custom Instrumentation with Agent.💡 Spring Boot 4 Native Starter Users: Use Micrometer APIs (
io.micrometer.tracing.*) instead of OpenTelemetry APIs (io.opentelemetry.api.*). See the Spring Boot 4 guide for API comparisons.
Manual Span Creation
Create custom spans for business logic:
package com.example.service;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
private final Tracer tracer;
public PaymentService(OpenTelemetry openTelemetry) {
this.tracer = openTelemetry.getTracer("payment-service");
}
public PaymentResult processPayment(PaymentRequest request) {
Span span = tracer.spanBuilder("process_payment")
.setAttribute("payment.method", request.getMethod())
.setAttribute("payment.amount", request.getAmount())
.setAttribute("payment.currency", request.getCurrency())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Business logic
validatePayment(request);
PaymentResult result = chargeCustomer(request);
span.setAttribute("payment.transaction_id", result.getTransactionId());
span.setStatus(StatusCode.OK);
return result;
} catch (PaymentException e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, "Payment failed");
throw e;
} finally {
span.end();
}
}
}
Adding Span Attributes
Enrich spans with business-specific attributes:
package com.example.service;
import io.opentelemetry.api.trace.Span;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public Order createOrder(OrderRequest request) {
Span currentSpan = Span.current();
// Add custom attributes to current span
currentSpan.setAttribute("order.user_id", request.getUserId());
currentSpan.setAttribute("order.item_count", request.getItems().size());
currentSpan.setAttribute("order.total_value", request.getTotalValue());
currentSpan.setAttribute("order.payment_method", request.getPaymentMethod());
// Business logic
Order order = saveOrder(request);
currentSpan.setAttribute("order.id", order.getId());
currentSpan.addEvent("Order created successfully");
return order;
}
}
Exception Handling and Error Recording
Properly record exceptions in spans:
package com.example.service;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public User findUserById(Long id) {
Span span = Span.current();
try {
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
span.setAttribute("user.id", user.getId());
span.setAttribute("user.role", user.getRole());
return user;
} catch (UserNotFoundException e) {
// Record exception with full stack trace
span.recordException(e);
span.setStatus(StatusCode.ERROR, "User not found");
throw e;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, "Unexpected error");
throw new ServiceException("Failed to find user", e);
}
}
}
Async Operation Instrumentation
Instrument async operations with proper context propagation:
package com.example.service;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class AsyncService {
private final Tracer tracer;
public AsyncService(OpenTelemetry openTelemetry) {
this.tracer = openTelemetry.getTracer("async-service");
}
@Async
public CompletableFuture<String> processAsync(String input) {
// Capture current context
Context context = Context.current();
return CompletableFuture.supplyAsync(() -> {
// Restore context in async thread
try (var scope = context.makeCurrent()) {
Span span = tracer.spanBuilder("async_processing")
.setAttribute("input.length", input.length())
.startSpan();
try (var spanScope = span.makeCurrent()) {
String result = performWork(input);
span.setStatus(StatusCode.OK);
return result;
} finally {
span.end();
}
}
});
}
}
Custom Metrics
Create custom business metrics:
💡 Spring Boot 4 Native Starter: Use Micrometer's
MeterRegistryfor metrics instead of OpenTelemetry'sMeter. Example:meterRegistry.counter("orders.created").increment(). The metrics are automatically exported via OTLP.
package com.example.metrics;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.LongHistogram;
import org.springframework.stereotype.Component;
@Component
public class BusinessMetrics {
private final LongCounter orderCounter;
private final LongHistogram orderValueHistogram;
private final Meter meter;
public BusinessMetrics(OpenTelemetry openTelemetry) {
this.meter = openTelemetry.getMeter("business-metrics");
this.orderCounter = meter.counterBuilder("orders.created")
.setDescription("Total number of orders created")
.build();
this.orderValueHistogram = meter.histogramBuilder("order.value")
.setDescription("Distribution of order values")
.ofLongs()
.build();
}
public void recordOrder(Order order) {
orderCounter.add(1, Attributes.builder()
.put("order.status", order.getStatus())
.put("order.payment_method", order.getPaymentMethod())
.build());
orderValueHistogram.record(order.getTotalValue(), Attributes.builder()
.put("order.currency", order.getCurrency())
.build());
}
}
Running Your Application
Choose the deployment method that matches your environment:
- Local Development
- Production JAR
- Docker
Run your Spring Boot application locally with OpenTelemetry:
# Set environment variables
export OTEL_SERVICE_NAME=my-service-dev
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Run with Maven
mvn spring-boot:run
# Or run with Gradle
./gradlew bootRun
Build and run as a standalone JAR:
# Build the application
mvn clean package -DskipTests
# Run with production configuration
java -jar target/my-service-1.0.0.jar \
--spring.profiles.active=prod \
-Dotel.service.name=my-service \
-Dotel.exporter.otlp.endpoint=https://scout-collector.example.com:4318
Build and run in Docker:
# Build Docker image
docker build -t my-service:1.0.0 .
# Run container
docker run -d \
--name my-service \
-p 8080:8080 \
-e OTEL_SERVICE_NAME=my-service \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318 \
-e OTEL_RESOURCE_ATTRIBUTES=environment=development \
my-service:1.0.0
Troubleshooting
Common Issues and Solutions
1. No Traces Appearing in Scout
Symptoms: Application runs but no traces visible in base14 Scout.
Solutions:
# Verify collector endpoint is reachable
curl -v http://scout-collector:4318/health
# Enable debug logging
export OTEL_LOG_LEVEL=debug
export LOGGING_LEVEL_IO_OPENTELEMETRY=DEBUG
# Add debug logging
logging.level.io.opentelemetry=DEBUG
logging.level.io.opentelemetry.exporter=TRACE
2. Two HTTP Server Spans per Request
Symptoms: Each request shows the starter's span, such as GET /users/, and
a second span from the org.springframework.boot scope, such as
http get /users/.
Cause: io.micrometer:micrometer-tracing-bridge-otel is on the classpath
next to the starter. On Spring Boot 3.x it turns on Micrometer Observation
tracing, which creates its own server spans.
Solutions:
- Remove
micrometer-tracing-bridge-otelfrom the build - Or set
management.tracing.enabled=false
On Spring Boot 4 the bridge alone creates no spans, so it can be removed there too.
3. ClassNotFoundException or NoClassDefFoundError
Symptoms: Application fails to start with missing OpenTelemetry classes.
Solutions:
<!-- Ensure BOM is imported in pom.xml -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>2.33.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
4. High Memory Usage
Symptoms: Application memory usage increases significantly after adding instrumentation.
Solutions:
# Reduce batch size and queue size
otel.bsp.max.queue.size=1024
otel.bsp.max.export.batch.size=256
otel.bsp.schedule.delay=5000
5. Missing Database Query Spans
Symptoms: HTTP requests traced but database queries not visible.
Solutions:
# Enable JDBC instrumentation explicitly
otel.instrumentation.jdbc.enabled=true
otel.instrumentation.jdbc-datasource.enabled=true
# Show SQL in spans (dev only)
otel.instrumentation.jdbc.statement-sanitizer.enabled=false
6. Verification Test
Test that instrumentation is working correctly:
package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class TelemetryTest {
@Autowired
private OpenTelemetry openTelemetry;
@Test
void testTelemetryConfiguration() {
assertThat(openTelemetry).isNotNull();
Tracer tracer = openTelemetry.getTracer("test");
Span span = tracer.spanBuilder("test-span").startSpan();
assertThat(span).isNotNull();
assertThat(span.isRecording()).isTrue();
span.end();
}
}
Security Considerations
Protecting sensitive data in telemetry is critical for compliance and security.
Sensitive Data Masking
Mask sensitive data in span attributes:
package com.example.security;
import io.opentelemetry.api.trace.Span;
import org.springframework.stereotype.Component;
@Component
public class SensitiveDataMasker {
public void addUserAttributes(Span span, User user) {
span.setAttribute("user.id", user.getId());
span.setAttribute("user.role", user.getRole());
// BAD: Exposing PII
// span.setAttribute("user.email", user.getEmail());
// span.setAttribute("user.phone", user.getPhone());
// GOOD: Masked or hashed
span.setAttribute("user.email_domain", extractDomain(user.getEmail()));
span.setAttribute("user.phone_country", extractCountryCode(user.getPhone()));
}
private String extractDomain(String email) {
return email.substring(email.indexOf('@') + 1);
}
}
SQL Query Obfuscation
Ensure SQL queries don't leak sensitive data:
# Enable SQL statement sanitization (production)
otel.instrumentation.jdbc.statement-sanitizer.enabled=true
# Disable raw SQL in spans
otel.instrumentation.jdbc.statement.enabled=false
HTTP Header Filtering
Filter sensitive HTTP headers from traces:
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SecurityConfig {
@Bean
public WebMvcConfigurer headerFilterConfigurer() {
return new WebMvcConfigurer() {
// Automatically filtered by OpenTelemetry:
// - Authorization
// - Cookie
// - Set-Cookie
// - X-API-Key
// Custom filter for additional headers
// Configure via: otel.instrumentation.http.capture-headers.server.request
};
}
}
# Specify headers to capture (whitelist approach)
otel.instrumentation.http.capture-headers.server.request=X-Request-ID,X-Correlation-ID
otel.instrumentation.http.capture-headers.server.response=X-Response-Time
Compliance Considerations
For GDPR, HIPAA, and PCI-DSS compliance:
- Disable PII capture: Never add email, phone, SSN, or credit card data to spans
- Use span redaction: Implement custom span processors to redact data
- Audit trace data: Regularly review exported spans for sensitive information
- Implement data retention policies: Configure Scout to delete traces after required period
- Encrypt in transit: Always use HTTPS/TLS for OTLP exporter endpoints
package com.example.config;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.SpanProcessor;
import java.util.Set;
public class ComplianceSpanProcessor implements SpanProcessor {
private static final Set<String> SENSITIVE_KEYS = Set.of(
"user.email", "user.phone", "credit_card", "ssn", "password"
);
@Override
public void onStart(Context parentContext, ReadWriteSpan span) {
// No-op on start
}
@Override
public boolean isStartRequired() {
return false;
}
@Override
public void onEnd(ReadableSpan span) {
// Redact sensitive attributes before export
AttributesBuilder builder = Attributes.builder();
span.getAttributes().forEach((key, value) -> {
if (SENSITIVE_KEYS.contains(key.getKey())) {
builder.put(key.getKey(), "[REDACTED]");
} else {
builder.put((AttributeKey<Object>) key, value);
}
});
}
@Override
public boolean isEndRequired() {
return true;
}
}
Performance Considerations
Understanding and optimizing the performance impact of instrumentation.
Expected Performance Impact
Typical overhead when using OpenTelemetry with Spring Boot:
| Metric | Impact | Notes |
|---|---|---|
| Latency | +1-5ms per request | Mostly from span creation and export |
| CPU Usage | +2-5% | Varies with trace volume |
| Memory | +50-200 MB | For span buffers and exporters |
| Network | ~1-5 KB/span | Depends on attribute count |
Factors affecting performance:
- Number of instrumented operations per request
- Number of custom attributes added
- Batch processor configuration
- Network latency to collector
Optimization Best Practices
1. Optimize Batch Processing
BatchSpanProcessor.builder(exporter)
.setMaxQueueSize(2048) // Increase queue size
.setMaxExportBatchSize(512) // Larger batches
.setScheduleDelay(Duration.ofSeconds(5)) // Less frequent exports
.build();
2. Limit Attribute Count
// BAD: Too many attributes
span.setAttribute("item_1", value1);
span.setAttribute("item_2", value2);
// ... 100 more attributes
// GOOD: Aggregate
span.setAttribute("item_count", items.size());
span.setAttribute("total_value", calculateTotal(items));
3. Use Conditional Instrumentation
public void processOrder(Order order) {
// Only create detailed spans for high-value orders
if (order.getValue() > 10000) {
Span span = tracer.spanBuilder("process_high_value_order")
.setAttribute("order.value", order.getValue())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Detailed instrumentation
} finally {
span.end();
}
} else {
// Regular processing without extra spans
processRegularOrder(order);
}
}
4. Disable Unnecessary Instrumentation
# Disable specific instrumentations to reduce overhead
otel.instrumentation.spring-webmvc.enabled=true
otel.instrumentation.jdbc.enabled=true
otel.instrumentation.kafka.enabled=true
# Disable if not needed
otel.instrumentation.logback.enabled=false
otel.instrumentation.annotations.enabled=false
FAQ
Choosing Between Approaches
Should I use the Java Agent, SDK integration, or Spring Boot 4 native?
For most cases:
- Quick start / Legacy apps: Use Java Agent (zero code changes, fastest setup)
- Production with flexibility: Use OpenTelemetry SDK Integration (full API access, Spring config, GraalVM support)
- Spring Boot 4, Micrometer-first teams: Consider Spring Boot 4 Native (Spring-native conventions)
See the Choosing Your Approach section for detailed comparison and decision guide.
What is the difference between the Java Agent and the OpenTelemetry SDK?
Java Agent:
- Zero code changes - attach JAR at startup
- 150+ libraries auto-instrumented
- Environment variables only for configuration
- Limited custom instrumentation (annotations only)
- May have version compatibility issues
OpenTelemetry SDK Integration:
- Requires adding dependencies
- ~30 libraries auto-instrumented
- Spring configuration (
application.yml) supported - Full OpenTelemetry API for custom instrumentation
- Better for GraalVM native-image
Can I migrate from Java Agent to OpenTelemetry SDK Integration later?
Yes! Start with the Java Agent for zero-code setup, then migrate to the OpenTelemetry SDK Integration when you need:
- Custom span processors or exporters
- Programmatic span creation (not just annotations)
- Spring Boot native configuration files
- GraalVM native-image compilation
The migration just requires adding dependencies and removing the -javaagent
flag.
Does the OpenTelemetry Spring Boot starter support Spring Boot 4?
The OpenTelemetry Spring Boot starter supports Spring Boot 4 from version 2.23.0. It is maintained by the OpenTelemetry project, separately from Spring Boot.
Can I use multiple instrumentation approaches together?
No, you should use only one approach. They provide overlapping functionality and using multiple approaches will cause conflicts:
- Don't use Java Agent + OpenTelemetry SDK Integration together
- Don't use OpenTelemetry SDK Integration + Spring Boot 4 Native together
- Choose one based on your needs (see decision guide)
Does the Java Agent work with GraalVM native-image?
No, the Java Agent has poor support for GraalVM native-image compilation due to bytecode manipulation incompatibilities. If you need native-image, use the OpenTelemetry SDK Integration or Spring Boot 4 Native starter instead.
General Questions
What is the minimum Spring Boot version required for OpenTelemetry?
The OpenTelemetry Spring Boot starter supports Spring Boot 2.6 and later, 3.1 and later, and 4.x. Spring Boot 3.0 is not supported. Spring Boot 4 needs starter 2.23.0 or later.
Does OpenTelemetry work with Java 25?
Java 25 works with both the OpenTelemetry Spring Boot starter and the Java agent. The base14 Java 25 examples run on Spring Boot 4.1.2 with starter 2.33.0 or agent 2.33.1.
What is the difference between OpenTelemetry and Java Flight Recorder?
OpenTelemetry focuses on distributed tracing across microservices with standardized telemetry, while JFR provides deep JVM-level profiling. Java 25's JEP 520 adds method tracing to JFR, which can complement OpenTelemetry. You can use both: JFR for low-level JVM metrics and OpenTelemetry for distributed traces.
How much performance overhead does OpenTelemetry add?
Typical overhead is 1-5ms per request, 2-5% CPU usage, and 50-200MB memory. Impact varies based on instrumentation scope and collector network latency.
Can I use OpenTelemetry with Spring Boot 2.x?
The current 2.x starter supports Spring Boot 2.6 and 2.7 on Java 8 or later.
Use spring-boot-starter-web instead of spring-boot-starter-webmvc, and keep
the instrumentation BOM version the same as on Spring Boot 3 and 4.
Configuration Questions
How do I prevent specific endpoints from being traced?
Drop those spans in the OpenTelemetry Collector with a filter processor. The starter has no per-path exclusion setting. The base14 examples drop Actuator spans this way:
processors:
filter/noisy:
error_mode: ignore
traces:
span:
- 'IsMatch(name, ".*/actuator/.*")'
Add filter/noisy to the traces pipeline's processors list.
Can I send traces to multiple backends?
Yes, configure multiple exporters programmatically:
SpanExporter compositeExporter = SpanExporter.composite(
OtlpHttpSpanExporter.builder().setEndpoint("http://scout:4318").build(),
OtlpHttpSpanExporter.builder().setEndpoint("http://backup:4318").build()
);
Troubleshooting Questions
Why are my database queries not appearing in traces?
Enable JDBC instrumentation: otel.instrumentation.jdbc.enabled=true and verify
DataSource is created after OpenTelemetry initialization.
Why duplicate spans for the same operation?
Multiple instrumentation libraries may overlap. Disable manual instrumentation
(remove @WithSpan annotations) where auto-instrumentation already exists.
Spring-Specific Questions
How does OpenTelemetry work with Spring Cloud?
OpenTelemetry integrates seamlessly with Spring Cloud components like Feign clients, Spring Cloud Gateway, and Sleuth. For Spring Cloud 2021.0.3+, OpenTelemetry can replace Sleuth entirely.
Can I use OpenTelemetry with Spring WebFlux?
Yes, OpenTelemetry fully supports reactive Spring WebFlux applications with automatic context propagation across reactive operators.
How do I instrument multi-tenant Spring Boot applications?
Add tenant information as span attributes:
Span.current().setAttribute("tenant.id", SecurityContextHolder.getContext().getTenantId());
Configure tenant-based filtering as needed.
What's Next
Once instrumented, you can view Spring Boot traces and metrics in Scout — monitor REST endpoints, JPA queries, and async operations from a single dashboard.
Advanced Topics
- Custom Java Instrumentation - Deep dive into manual instrumentation, custom span processors, and advanced tracing patterns
Deployment Guides
- Kubernetes Helm Setup - Deploy Scout Collector on Kubernetes with Helm charts
- Docker Compose Setup - Run Scout Collector locally with Docker Compose
- AWS ECS Deployment - Deploy instrumented Spring Boot apps on AWS ECS with Scout Collector
- Scout Collector Configuration - Configure the OpenTelemetry Collector for production use
Related Guides
- Spring Boot vs Alternatives - JVM framework comparison guide
- All framework guides - Auto-instrumentation overview for every language
Complete Example
For a fully working Spring Boot application with OpenTelemetry instrumentation, refer to our example repository:
Spring Boot OpenTelemetry Example
Another example, on Java 17 with MySQL: spring-boot-java17-mysql
The example includes:
- Spring Boot 4.1.2 REST API built with Gradle
opentelemetry-spring-boot-starter2.33.0 through the instrumentation BOM- Spring Data JPA on PostgreSQL with JDBC spans
- Logback log records exported over OTLP with trace correlation
- Docker Compose with PostgreSQL and the OpenTelemetry Collector
scripts/test-api.shandscripts/verify-scout.shto exercise the API and check the exported telemetry
Quick Start with Example
# Clone the repository
git clone https://github.com/base-14/examples.git
cd examples/java/spring-boot-java25-postgresql
# Start the app, PostgreSQL and the Collector
docker compose up -d --build
# Exercise the API
curl http://localhost:8080/users/
./scripts/test-api.sh
The example demonstrates:
- Automatic HTTP request tracing
- Database query instrumentation
- Log correlation with trace and span IDs
- Runtime and HTTP metrics export
- Production-ready configuration patterns
References
- OpenTelemetry Traces Documentation
- Sample application: Spring Boot OTel Instrumentation
Related Guides
- Quarkus Instrumentation - Supersonic JVM framework with native OpenTelemetry support
- Micronaut Instrumentation - Compile-time DI JVM framework
- Ktor Instrumentation - Kotlin-native async web framework
- Java Custom Instrumentation - Manual spans and advanced patterns
- Spring AI LLM Observability - Spring AI with three-layer OTel instrumentation