Spring Boot Observability in 2026: OpenTelemetry, Distributed Tracing en Sollicitatievragen

Beheers Spring Boot observability met OpenTelemetry en Micrometer Tracing. Configureer OTLP-export, gebruik de Observation API en bereid je voor op technische sollicitatiegesprekken.

Spring Boot Observability in 2026: OpenTelemetry, Distributed Tracing en Sollicitatievragen

Spring Boot observability combineert logging, metrics en distributed tracing in een uniform systeem dat onthult hoe requests door microservices stromen. Vanaf Spring Boot 3 heeft het framework Micrometer Tracing geadopteerd (ter vervanging van Spring Cloud Sleuth) en de Observation API geintroduceerd, die een enkel instrumentatiepunt biedt dat zowel metrics als traces produceert.

Het Observation API Pattern

Spring Boot raadt aan om Observation.observe() te gebruiken in plaats van OpenTelemetry direct aan te roepen. Een enkele instrumentatie-aanroep produceert metrics via Micrometer en traces via de OpenTelemetry bridge, waardoor code-duplicatie vermindert en consistente tag-naamgeving over alle signalen wordt gegarandeerd.

Hoe de Observation API Micrometer en OpenTelemetry Verbindt

De Observation API fungeert als een facade over zowel metrics als tracing. Wanneer code Observation.createNotStarted() aanroept, routeert Spring Boot de observation naar geregistreerde handlers: MeterObservationHandler voor Micrometer metrics en TracingObservationHandler voor distributed traces. Deze architectuur betekent: eenmaal instrumenteren, overal exporteren.

De bridge-dependency micrometer-tracing-bridge-otel verbindt Micrometer Tracing met de SDK van OpenTelemetry. Traces stromen door de SdkTracerProvider van OpenTelemetry en exporteren via OTLP naar backends zoals Jaeger, Tempo of elke OpenTelemetry-compatibele collector.

ObservabilityConfig.javajava
@Configuration
public class ObservabilityConfig {

    @Bean
    public ObservationRegistryCustomizer<ObservationRegistry> addLowCardinalityTags() {
        return registry -> registry.observationConfig()
            .observationHandler(new ObservationTextPublisher()); // Logs observations to console
    }
}

De bovenstaande configuratie registreert een ObservationHandler die elke observation logt. In productie sturen de automatisch geconfigureerde handlers data naar Micrometer registries en OpenTelemetry exporters zonder extra code.

Vereiste Dependencies voor Spring Boot 3.4 Tracing

Spring Boot 3.4 vereist expliciete dependencies voor distributed tracing. De spring-boot-starter-actuator levert de Observation API, maar tracing heeft de OpenTelemetry bridge en een exporter nodig.

xml
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-otel</artifactId>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>
</dependencies>

Het micrometer-tracing-bridge-otel artifact verbindt Micrometer Tracing met de API van OpenTelemetry. De opentelemetry-exporter-otlp stuurt spans naar collectors via het OTLP-protocol over HTTP of gRPC. Spring Boot beheert versie-uitlijning via dependency management.

OTLP Export naar Jaeger of Tempo Configureren

Spring Boot configureert automatisch een OtlpHttpSpanExporter wanneer de OTLP-dependency aanwezig is. De exporter stuurt traces naar het endpoint gespecificeerd in application.yml.

yaml
# application.yml
management:
  tracing:
    sampling:
      probability: 1.0  # 100% sampling for dev, reduce in production
  otlp:
    tracing:
      endpoint: http://localhost:4318/v1/traces
  opentelemetry:
    resource-attributes:
      service.name: order-service
      deployment.environment: staging

De resource-attributes koppelen metadata aan elke span. Backends zoals Grafana Tempo en Jaeger gebruiken deze attributen om traces te groeperen op service en omgeving. Het instellen van sampling.probability op 1.0 vangt alle requests tijdens ontwikkeling, maar productiesystemen samplen typisch tussen 1% en 10% om opslagkosten te beheersen.

Custom Observations Maken met Low en High Cardinality Tags

Observations ondersteunen twee tag-types: low cardinality voor metrics (begrensde waarden zoals HTTP-methodes of statuscodes) en high cardinality voor traces (onbegrensde waarden zoals gebruikers-IDs of request-IDs).

PaymentService.javajava
@Service
public class PaymentService {

    private final ObservationRegistry observationRegistry;

    public PaymentService(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    public PaymentResult processPayment(PaymentRequest request) {
        return Observation.createNotStarted("payment.process", observationRegistry)
            .lowCardinalityKeyValue("payment.method", request.getMethod().name())  // enum: CARD, BANK_TRANSFER
            .lowCardinalityKeyValue("currency", request.getCurrency())              // bounded: USD, EUR, GBP
            .highCardinalityKeyValue("payment.id", request.getPaymentId())          // unique per request
            .highCardinalityKeyValue("customer.id", request.getCustomerId())        // high cardinality
            .observe(() -> executePayment(request));
    }

    private PaymentResult executePayment(PaymentRequest request) {
        // Payment gateway call
        return new PaymentResult(true, "TXN-" + UUID.randomUUID());
    }
}

Low cardinality tags verschijnen in zowel metrics als traces. High cardinality tags verschijnen alleen in traces omdat metrics met onbegrensde dimensies de opslag laten exploderen. Dit onderscheid voorkomt Prometheus cardinaliteitsbommen terwijl trace-details rijk blijven.

Sollicitatievraag: Tag Cardinaliteit

Interviewers vragen vaak waarom bepaalde tags van metrics moeten worden uitgesloten. Het antwoord betreft cardinaliteit: een gebruikers-ID tag op een metric creëert één time series per gebruiker, potentieel miljoenen series. Monitoring backends worstelen met hoge cardinaliteit, wat leidt tot geheugenuitputting en trage queries. Traces verwerken hoge cardinaliteit via sampling, waardoor ze de juiste plek zijn voor request-specifieke identifiers.

Klaar om je Spring Boot gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Trace Context Propageren Over Asynchrone Grenzen

Distributed tracing vereist context propagatie. HTTP headers dragen trace IDs tussen services, maar asynchrone operaties zoals @Async methodes of CompletableFuture chains kunnen context verliezen als ze niet geconfigureerd zijn.

Spring Boot 3.4 biedt automatische propagatie voor reactive streams en @Async methodes wanneer geconfigureerd:

yaml
# application.yml
spring:
  reactor:
    context-propagation: auto
  task:
    execution:
      propagate-context: true

Voor custom TaskExecutor beans worden deze gewrapt met ContextPropagatingTaskDecorator:

AsyncConfig.javajava
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
        executor.initialize();
        return executor;
    }
}

De decorator kopieert de huidige Observation en trace context naar de asynchrone thread. Zonder deze decorator hebben spans die in asynchrone tasks worden gestart geen parent, wat de trace tree verbreekt.

@Observed en @NewSpan Annotaties Gebruiken

Spring Boot 3.4 ondersteunt declaratieve observability via annotaties. Annotatieverwerking wordt ingeschakeld door de AspectJ weaver toe te voegen:

xml
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
yaml
# application.yml
management:
  observations:
    annotations:
      enabled: true
InventoryService.javajava
@Service
public class InventoryService {

    @Observed(name = "inventory.check", contextualName = "checkStock")
    public StockLevel checkStock(String productId) {
        // Database query
        return new StockLevel(productId, 42);
    }

    @NewSpan("inventory.reserve")  // Creates a child span under the current trace
    public ReservationResult reserveStock(String productId, int quantity) {
        // Update inventory
        return new ReservationResult(true, "RES-" + System.currentTimeMillis());
    }
}

@Observed creëert zowel een metric (timer) als een span. @NewSpan creëert alleen een span, nuttig wanneer de operatie al ergens anders metrics heeft. Beide annotaties verwerken automatisch exception recording en span status.

Auto-geïnstrumenteerde Componenten in Spring Boot 3.4

Spring Boot 3.4 auto-instrumenteert verschillende componenten zonder codewijzigingen:

ComponentObservation NaamWat het Vangt
Spring MVChttp.server.requestsRequest pad, methode, status, exception
WebClienthttp.client.requestsUitgaande HTTP calls
RestClienthttp.client.requestsUitgaande HTTP calls (Spring 6.1+)
Spring Kafkaspring.kafka.listenerConsumer group, topic, partition
Spring Data JPAspring.data.repositoryRepository methode, query tijd
Scheduled Tasksspring.schedulingTask naam, uitvoeringstijd

De Spring Boot Actuator documentatie vermeldt alle auto-geïnstrumenteerde componenten. Third-party libraries zoals Datasource Micrometer voegen JDBC query tracing toe.

Observations Filteren om Ruis te Verminderen

Health checks, readiness probes en statische resources genereren ruis in traces. Deze worden uitgefilterd met predicates:

ObservationFilterConfig.javajava
@Configuration
public class ObservationFilterConfig {

    @Bean
    public ObservationPredicate noHealthChecks() {
        return (name, context) -> !name.equals("http.server.requests")
            || !isHealthEndpoint(context);
    }

    private boolean isHealthEndpoint(Observation.Context context) {
        if (context instanceof ServerRequestObservationContext http) {
            String path = http.getCarrier().getRequestURI();
            return path.startsWith("/actuator/health") || path.startsWith("/actuator/ready");
        }
        return false;
    }
}

Alternatief kunnen observations op naam worden uitgeschakeld in de configuratie:

yaml
# application.yml
management:
  observations:
    enable:
      spring.security: false  # Disable Spring Security observations
      http.server.requests.actuator: false  # Custom predicate name
Sollicitatiecontext: Trace Sampling vs. Filtering

Sampling reduceert het percentage verzamelde traces over alle endpoints. Filtering verwijdert specifieke endpoints volledig. Filtering wordt gebruikt voor endpoints die nooit diagnostische waarde bieden (health checks, metrics scraping). Sampling wordt gebruikt om kosten te beheersen terwijl representatieve data behouden blijft.

Logs Correleren met Trace IDs

Spring Boot 3.4 voegt automatisch trace en span IDs toe aan de MDC (Mapped Diagnostic Context) wanneer Micrometer Tracing actief is. Logback en Log4j2 kunnen deze IDs opnemen in de log output:

xml
<!-- logback-spring.xml -->
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - traceId=%X{traceId} spanId=%X{spanId} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

Met dit pattern bevat elke logregel de trace ID. Zoeken in logs op trace ID retourneert alle berichten van een enkele request over alle services, waardoor logs worden gecorreleerd met spans in Jaeger of Tempo.

Veelvoorkomende Sollicitatievragen over Spring Boot Observability

Interviewers beoordelen zowel conceptueel begrip als praktische ervaring met observability. Deze vragen komen frequent voor in senior backend rollen.

V: Wat is het verschil tussen Micrometer Tracing en OpenTelemetry?

Micrometer Tracing is een vendor-neutrale API voor distributed tracing, vergelijkbaar met hoe Micrometer metrics abstraheert. OpenTelemetry is een specifieke implementatie en protocol. Spring Boot gebruikt Micrometer Tracing als API en bridget naar OpenTelemetry voor export via micrometer-tracing-bridge-otel. Deze gelaagdheid laat applicaties van backend wisselen zonder codewijzigingen.

V: Waarom raadt Spring de Observation API aan boven directe OpenTelemetry instrumentatie?

De Observation API biedt een enkel instrumentatiepunt dat zowel metrics als traces produceert. Directe OpenTelemetry calls produceren alleen traces. Het gebruik van Observation.observe() genereert een timer metric en een span vanuit dezelfde code, wat duplicatie vermindert en consistente naamgeving garandeert.

V: Hoe debug je een trace die een gap tussen spans toont?

Gaps duiden op ontbrekende instrumentatie of verbroken context propagatie. Controleer of de code asynchrone operaties gebruikt zonder ContextPropagatingTaskDecorator. Verifieer dat HTTP clients geïnstrumenteerd zijn (WebClient, RestClient, of een handmatig gewrapte RestTemplate). Voor message queues, bevestig dat trace headers propageren via message properties.

V: Wat gebeurt er als je een high cardinality tag aan een metric toevoegt?

Elke unieke tag waarde creëert een nieuwe time series. Een gebruikers-ID tag met miljoenen gebruikers creëert miljoenen series, wat geheugen uitput in Prometheus of andere TSDB backends. De oplossing is het gebruik van highCardinalityKeyValue() in plaats van lowCardinalityKeyValue(), wat de tag alleen aan traces toevoegt waar hoge cardinaliteit verwacht wordt.

Een Distributed Tracing Pipeline Bouwen met Docker Compose

Een lokale observability stack helpt bij het valideren van instrumentatie voor deployment naar productie. Dit voorbeeld gebruikt de OpenTelemetry Collector en Jaeger:

yaml
# docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.102.0
    command: ["--config", "/etc/otel-collector-config.yml"]
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    ports:
      - "4318:4318"   # OTLP HTTP
      - "4317:4317"   # OTLP gRPC

  jaeger:
    image: jaegertracing/jaeger:2.3
    ports:
      - "16686:16686"  # UI
    environment:
      - COLLECTOR_OTLP_ENABLED=true

  app:
    build: .
    environment:
      - MANAGEMENT_OTLP_TRACING_ENDPOINT=http://otel-collector:4318/v1/traces
    depends_on:
      - otel-collector
yaml
# otel-collector-config.yml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger]

De collector ontvangt spans van de Spring Boot applicatie en stuurt ze door naar Jaeger. Deze architectuur maakt het mogelijk om exporters toe te voegen (Tempo, Zipkin, cloud backends) zonder applicatiecode te wijzigen.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Belangrijke Inzichten voor Spring Boot Observability

  • De Observation API unificeert metrics en tracing: één Observation.observe() call produceert zowel een timer metric als een span, waardoor dubbele instrumentatie wordt geëlimineerd.
  • Voeg micrometer-tracing-bridge-otel en opentelemetry-exporter-otlp toe om OTLP export in te schakelen. Spring Boot 3.4 configureert automatisch de HTTP exporter met het endpoint uit management.otlp.tracing.endpoint.
  • Gebruik lowCardinalityKeyValue() voor dimensies die in metrics verschijnen (begrensde waarden). Gebruik highCardinalityKeyValue() voor trace-only data (gebruikers-IDs, request-IDs).
  • Schakel context propagatie in voor asynchrone code met spring.task.execution.propagate-context=true en wrap custom executors met ContextPropagatingTaskDecorator.
  • Filter luidruchtige endpoints zoals /actuator/health met ObservationPredicate beans om traces gefocust te houden op business operaties.
  • Log patterns moeten %X{traceId} bevatten om logregels te correleren met distributed traces in je observability backend.
  • Sollicitatievragen over observability focussen op cardinaliteit, context propagatie failures, en het onderscheid tussen de Observation API en direct OpenTelemetry gebruik.
Dagelijkse challenge

Zie jij de bug in Spring Boot?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 19 september 2026

Delen

Gerelateerde artikelen