Skip to content

[Spring Boot Starter] No HTTP server spans on Spring Boot 4 / Spring Framework 7 / Spring Web MVC 7 (only WebMvc6 instrumentation present) #18622

Description

@RichardLindhout

Component(s)

instrumentation/spring/spring-boot-autoconfigure, instrumentation/spring/spring-webmvc

What happened?

opentelemetry-spring-boot-starter 2.27.0 boots cleanly under Spring Boot 4.0.6 (OpenTelemetry Spring Boot starter (2.27.0) has been started appears in the log, OpenTelemetryAppender installs and ships logs to the configured OTLP endpoint), but no span.kind=server spans are produced for incoming HTTP requests.

Verified end-to-end on a staging environment by sending 500+ requests to controller endpoints (mix of 200s and 404s). Resulting events in the OTLP backend (Honeycomb) for the affected service.version:

signal count
logs (INFO/WARN/ERROR via the OTEL appender) 40+
spans of any kind 0
HTTP server spans 0

Same exercise on the prior production build (Spring Boot 3.3.11 + opentelemetry-spring-boot-starter 2.17.0, identical OTLP collector + backend) emits server spans normally (http get /actuator/health, secured request, controller spans, etc.).

A/B'd with -Dspring.aot.enabled=true and =false on the SB4 build — same zero server spans either way, ruling out Spring AOT as the cause.

Also tried the OTEL Java agent v2.27.0 instead of the starter on the same SB4 image: agent emits gRPC client spans (google.pubsub.v1.Subscriber/StreamingPull, outbound HTTP GET) but still no server spans, so the gap is on the Spring MVC server-side instrumentation side, not the SDK or exporter.

Steps to reproduce

  1. Build a Spring Boot 4.0.6 app with spring-boot-starter-web (Tomcat 11, Spring Framework 7.0.7, Spring Web MVC 7 on the classpath).
  2. Add opentelemetry-spring-boot-starter:2.27.0 (via opentelemetry-instrumentation-bom:2.27.0).
  3. Configure OTLP export to any backend with otel.exporter.otlp.endpoint=…, otel.sdk.disabled=false, otel.traces.exporter=otlp.
  4. Start the app, hit any controller endpoint repeatedly.
  5. Observe: log records via the OTEL appender appear; HTTP server spans never do.

Expected Result

Each incoming HTTP request produces a span.kind=server span, the way SB3.x + starter 2.17.x does today.

Actual Result

Zero span.kind=server spans. The starter ships only opentelemetry-spring-webmvc-6.0:2.27.0-alpha and there is no spring-webmvc-7.0 artifact yet — looks like the WebMvc7 auto-configuration / instrumentation module is missing for Spring 7's MVC internals, which is what SB4 brings in.

Imports list pulled from META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports in opentelemetry-spring-boot-autoconfigure-2.27.0.jar confirms a SpringWebMvc6InstrumentationAutoConfiguration but no WebMvc7 peer:

…
SpringWebInstrumentationAutoConfiguration
SpringWebInstrumentationSpringBoot4AutoConfiguration
SpringWebMvc6InstrumentationAutoConfiguration   ← only WebMvc up to Spring 6
…

Where to look / suggested approach

Based on a quick read of the v2.27.0 sources, things look set up to "should just work" but evidently don't under Spring 7. Some pointers for where to investigate, in rough order of likelihood:

  1. WebMvc6 vs WebMvc7 module. The instrumentation/spring/spring-webmvc/spring-webmvc-6.0/ module's filter — WebMvcTelemetry / OpenTelemetryHandlerInterceptor — is instantiated by SpringWebMvc6InstrumentationAutoConfiguration. The auto-config's class condition is @ConditionalOnClass({Filter, OncePerRequestFilter, DispatcherServlet}), all of which Spring 7 keeps under the same FQNs, so the auto-config activates. Best guess: the bytecode/classloading of Spring 7's DispatcherServlet / HandlerExecutionChain / HandlerInterceptor internals diverged enough that the OpenTelemetryHandlerInterceptor doesn't end up wired into the chain, OR the Filter bean ordering relative to Spring Security 7's filter chain changed and the OTEL filter runs after the request is already complete.

    A spring-webmvc-7.0 peer module (mirroring the existing 6.0 one) with a proper smoke test against spring-boot-starter-parent:4.0.x would close this gap. Alternatively, if Spring 7's WebMvc internals are still compatible at the public-API level, the existing 6.0 module + a new SpringWebMvc7InstrumentationAutoConfiguration that registers the filter at a defensible order (Ordered.HIGHEST_PRECEDENCE + 1 or similar, before Spring Security's chain) might be enough.

  2. AOT-friendliness. Two related things bite once spring.aot.enabled=true is in play (which is increasingly the default for SB4 deployments):

    • @ConditionalOnEnabledInstrumentationInstrumentationPropertyEnabled resolves otel.instrumentation.<module>.enabled against the Environment at AOT processing time, when no profile is active and only application.properties is loaded. If a user has the property set in application-<profile>.properties, AOT misses it. We had to pass -Dspring.profiles.active=<our gcp profile> to process-aot to get a sane factory. Worth either documenting this gotcha in the starter README, or changing the Conditions to defer to runtime evaluation (Spring AOT supports Condition that opts out of AOT-time resolution by implementing AotApplicationContextInitializer or returning ConditionOutcome that indicates "uncertain").

    • OtelDisabled / OtelEnabled (in instrumentation/spring/spring-boot-autoconfigure/.../internal/) read otel.sdk.disabled the same way. Same fix surface as above.

    • Filter / HandlerInterceptor bean registration via FilterRegistrationBean should already be AOT-tracked by Spring Boot's RegisterReflectionForBinding machinery, but worth double-checking under Spring AOT in a smoke test with spring.aot.enabled=true set for the process-aot Maven goal AND the runtime JVM.

  3. Smoke-test gap. OtelSpringStarterSmokeTest ([Spring Starter] Spring boot 4 support #15459, Reduce flakiness in OtelSpringStarterSmokeTest.shouldSendTelemetry() #18476) asserts log + metric flow on SB4 but doesn't seem to assert on a span.kind=server span being produced for an inbound controller request. Adding that assertion would have caught this, and is the cheapest way to prevent the regression in 2.28+.

I'm happy to provide a minimal repro repo with Dockerfile + AOT cache + Spring Boot 4.0.6 + 7.0.7 if it's helpful — just say the word.

Java Version

Java 25.0.3 (Eclipse Temurin)

Component Version

  • opentelemetry-instrumentation-bom 2.27.0 (latest stable, April 21 2026 release)
  • opentelemetry-spring-boot-starter 2.27.0
  • opentelemetry-spring-webmvc-6.0 2.27.0-alpha (transitive)

Spring Versions

  • spring-boot-starter-parent 4.0.6
  • spring-context / spring-web / spring-webmvc / spring-core 7.0.7
  • Tomcat 11.0.21

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions