How to Map Micrometer Observation Tags to OpenTelemetry Semantic Conventions in Spring Boot 4.x?

Master the migration from Micrometer tracing to OpenTelemetry in Spring Boot 4.x. Learn to map legacy tags to OTel semantic conventions and maintain dashboard parity.

David George Hope |
A conceptual 3D illustration showing the transition and mapping from Micrometer metrics to OpenTelemetry semantic conventions in a Spring Boot environment.

Key Takeaways

  • Native OTel Transition: Spring Boot 4.x shifts from a Micrometer-first implementation to an OpenTelemetry-native observability model, necessitating a migration to OTel semantic conventions.
  • Manual Tag Mapping: Legacy Micrometer tags must be explicitly mapped to OTel attributes using custom ObservationHandlers or ObservationConventions to prevent data loss and dashboard breakage.
  • Configuration Namespace: The management.opentelemetry configuration namespace replaces legacy tracing properties, offering granular control over OTLP exports.
  • Dashboard Parity: Maintaining historical dashboard parity requires aligning resource attributes (like service.name) and span names with historical Micrometer-produced metadata.

Why did Spring Boot 4.x move to OpenTelemetry-native tracing?

Spring Boot 4.x moved to OpenTelemetry-native tracing to ensure industry-standard compatibility by adopting OTLP (OpenTelemetry Protocol) as the primary mechanism for exporting traces, metrics, and logs. This architectural shift removes the legacy autoconfigurations for Micrometer Tracing (formerly Sleuth), significantly reducing classpath bloat and eliminating the performance overhead previously caused by the heavy translation layer between Micrometer and Brave/OTel.

Definition: OpenTelemetry Native refers to an implementation where the underlying telemetry SDK is OpenTelemetry, and the framework (Spring Boot) configures it directly rather than wrapping it in a vendor-neutral abstraction layer that limits feature access.

By defaulting to the OpenTelemetry SDK, Spring Boot 4.x allows developers to leverage advanced features previously inaccessible or difficult to configure via the Micrometer abstraction. These include:

  • Advanced Sampling: Direct configuration of head-based and tail-based sampling policies.
  • Resource Providers: Native support for cloud-provider-specific resource detection (AWS, Azure, GCP) without custom beans.
  • Unified Configuration: A single starter, spring-boot-starter-opentelemetry, now manages the lifecycle of the OTel SDK, simplifying dependency management.

While the Micrometer API remains the primary instrumentation facade for application code (creating timers, counters, and spans), the backend implementation is now purely OpenTelemetry. This requires developers to update package imports from org.springframework.boot.metrics to org.springframework.boot.micrometer.metrics and manually bridge logging frameworks like Logback or Log4j2, as logs are no longer automatically correlated or exported without the OTel appender.

How do you map legacy Micrometer tags to OpenTelemetry attributes?

You map legacy Micrometer tags to OpenTelemetry attributes by implementing the ObservationConvention interface to redefine how high-cardinality and low-cardinality keys are translated into OTel spans and metrics. In Spring Boot 4.x, the default behavior adheres to OTel Semantic Conventions, which may differ from the naming schemes used in Spring Boot 2.x and 3.x (e.g., http.method vs. method).

To maintain dashboard parity, you must override the default conventions. For HTTP traffic, this involves extending DefaultServerRequestObservationConvention (or implementing the interface directly) and overriding the getLowCardinalityKeyValues method. This allows you to force the system to use legacy keys.

Definition: ObservationConvention is a Micrometer interface that extracts data from an Observation.Context and converts it into a set of KeyValues (tags/attributes) and a name.

Example: Preserving Legacy HTTP Tag Names

The following code demonstrates how to map the standard OTel http.request.method attribute back to the legacy method tag expected by older Grafana dashboards.

import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.stereotype.Component;

@Component
public class LegacyCompatibilityObservationConvention extends DefaultServerRequestObservationConvention {

    @Override
    public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
        // Get the standard OTel conventions
        KeyValues defaultValues = super.getLowCardinalityKeyValues(context);
        
        // Map 'http.request.method' back to legacy 'method'
        // and 'url.path' back to 'uri'
        return defaultValues.and(
            KeyValue.of("method", context.getCarrier().getMethod()),
            KeyValue.of("uri", context.getCarrier().getRequestURI())
        );
    }
}

In this approach, the KeyValue API ensures that your custom business metadata passes through the OTel bridge. Without this explicit mapping, the OTel bridge will only export attributes defined in the strict OTel Semantic Conventions, potentially dropping custom tags your alerts rely on.

Legacy Micrometer Tags vs. OTel Semantic Conventions

Legacy Micrometer Tag OTel Semantic Convention Notes
method http.request.method HTTP method (GET, POST, etc.)
uri url.path Request path
status http.response.status_code HTTP response code
exception error.type Exception class name
outcome http.response.status_code (derived) SUCCESS/CLIENT_ERROR/SERVER_ERROR mapped from status code
host server.address Target host
clientName server.address For client requests
db.type db.system Database type (postgresql, mysql, etc.)
db.statement db.query.text SQL or query string
messaging.system messaging.system No change
messaging.destination messaging.destination.name Queue or topic name

How do you implement a custom ObservationHandler for complex mapping?

To handle complex mapping scenarios where simple key renaming is insufficient, create a class implementing ObservationHandler<OtelTraceContext>. This interface allows you to intercept the lifecycle of an observation (start, stop, error) and programmatically inject OpenTelemetry attributes directly into the generic Span builder. This is essential when dealing with edge cases where Micrometer's flat tag structure conflicts with OpenTelemetry's nested or namespaced attribute requirements.

Definition: ObservationHandler is a listener in the Micrometer Observation API that reacts to lifecycle events of an Observation. It is the mechanism used to translate generic observations into specific backend signals (like OTel Spans).

While ObservationConvention is preferred for standard renaming, ObservationHandler provides access to the raw OTel Span and ReadWriteSpan objects. This allows for conditional logic based on runtime data that isn't available in the static context.

Example: Extracting Custom Headers to OTel Attributes

The following example demonstrates how to extract a custom header from a request and map it to a specific database attribute (db.system) or messaging attribute (messaging.destination), which might be required for topology views in APM tools.

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.tracing.otel.bridge.OtelTraceContext;
import io.opentelemetry.api.trace.Span;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;

@Component
public class CustomAttributeHandler implements ObservationHandler<OtelTraceContext> {

    @Override
    public void onStart(OtelTraceContext context) {
        // Access the underlying OTel Span
        Span otelSpan = context.getSpan();
        
        // Check if the carrier is an HTTP request
        if (context.getCarrier() instanceof HttpServletRequest request) {
            String tenantId = request.getHeader("X-Tenant-ID");
            
            if (tenantId != null) {
                // Manually set the attribute on the OTel span
                otelSpan.setAttribute("app.tenant.id", tenantId);
            }
            
            // Complex mapping logic: Map legacy path to specific db system attribute
            // if this is a database-related span
            if (request.getRequestURI().startsWith("/api/db")) {
                otelSpan.setAttribute("db.system", "postgresql");
            }
        }
    }

    @Override
    public boolean supportsContext(Observation.Context context) {
        // Only apply this handler to OTel trace contexts
        return context instanceof OtelTraceContext;
    }
}

This handler runs alongside the default handlers. By checking supportsContext, you ensure this logic only applies when the OTel bridge is active.

How do you maintain historical dashboard parity during the migration?

You maintain historical dashboard parity by explicitly configuring OTel Resource Attributes to replicate the application and instance tags that legacy Micrometer/Prometheus setups rely on. In Spring Boot 3.x and earlier, metrics were often tagged automatically with application=${spring.application.name}. In the OTel-native world of Spring Boot 4.x, these are defined as Resource Attributes.

To align these, you must configure the OTEL_RESOURCE_ATTRIBUTES environment variable or the management.opentelemetry.resource-attributes property. If these are missing, your data will appear under a generic service name (often unknown_service:java), breaking service-level aggregation in dashboards.

Aligning Span Names

A critical breaking change in Spring Boot 4.x is the span naming convention.

  • Legacy: Spans were often named generic strings like http.server.requests.
  • OTel Native: Spans follow the METHOD /path pattern (e.g., GET /api/users).

If your dashboards query by span name, you have two options: update the queries (recommended) or implement a SpanProcessor to rewrite names globally.

Comparison of Configuration Strategies

Feature Legacy Micrometer (Boot 3.x) OTel Native (Boot 4.x) Migration Action
Service Naming spring.application.name service.name (Resource Attribute) Set management.opentelemetry.resource-attributes.service.name=${spring.application.name}
Instance ID instanceId service.instance.id Map via OTEL_RESOURCE_ATTRIBUTES
Span Naming Interface-based Semantic Convention (Method + Path) Update dashboard queries or use SpanProcessor
Exporters management.tracing.exporter... management.otlp... Update configuration namespaces

What is the best way to handle OTLP exporter connection issues?

The best way to handle OTLP exporter connection issues—specifically the SDK attempting to connect to localhost:4317 when no collector exists—is to explicitly disable the default OTLP exporter via configuration. In Spring Boot 4.x, including spring-boot-starter-opentelemetry assumes you intend to export data immediately. If a collector is not present, the application will experience startup delays or log spam regarding "Connection refused."

To resolve this during local development or in environments using sidecars (where the endpoint might differ), use the following configuration:

# Disable OTLP export if no collector is available
management.otlp.tracing.export.enabled=false
management.otlp.metrics.export.enabled=false

# OPTIONAL: Enable logging exporter for local debugging
# This prints spans to the console instead of sending them over the network
management.otlp.tracing.export.step=10s

If you are developing locally and want to verify your tag mappings without running a full OpenTelemetry Collector infrastructure, you can configure a LoggingSpanExporter. This bean will print the full structure of the OTel spans to standard out, allowing you to verify that your ObservationConvention and ObservationHandler logic is correctly applying attributes like db.system or app.tenant.id.

@Bean
public SpanExporter loggingSpanExporter() {
    return LoggingSpanExporter.create();
}

Frequently Asked Questions

Can I keep using the old Micrometer Tracing dependencies in Spring Boot 4.x?

While technically possible via manual configuration, it is highly discouraged. Spring Boot 4.x has removed the autoconfiguration support for legacy Micrometer Tracing, meaning you would have to manually instantiate all tracers and bridges, negating the benefits of the framework's new OTel-native starter.

How do I handle log correlation (TraceID in logs) after the migration?

You must now use the OpenTelemetry Logback appender or the OTel logging bridge. Spring Boot 4.x no longer automatically injects Micrometer MDC keys (like traceId and spanId) into logs by default; the OTel SDK must be hooked into the logging framework to provide this correlation context.

What happens to my custom Micrometer ObservationFilter beans?

ObservationFilter beans still work for filtering out entire observations (e.g., ignoring health check endpoints). However, if your goal is to modify the content of the resulting OTel spans (adding/renaming tags), implementing an ObservationHandler or a generic OTel SpanProcessor is significantly more effective and type-safe in the new architecture.

Is there a tool to automate the property migration?

Yes, you should include the spring-boot-properties-migrator dependency in your build configuration during the upgrade process. This tool will analyze your application.properties or application.yaml at startup, provide console warnings for deprecated observability properties, and attempt to temporarily bridge them to the new management.opentelemetry namespace.