You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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).
Configure OTLP export to any backend with otel.exporter.otlp.endpoint=…, otel.sdk.disabled=false, otel.traces.exporter=otlp.
Start the app, hit any controller endpoint repeatedly.
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:
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.
AOT-friendliness. Two related things bite once spring.aot.enabled=true is in play (which is increasingly the default for SB4 deployments):
@ConditionalOnEnabledInstrumentation → InstrumentationPropertyEnabled 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.
Component(s)
instrumentation/spring/spring-boot-autoconfigure,instrumentation/spring/spring-webmvcWhat happened?
opentelemetry-spring-boot-starter2.27.0 boots cleanly under Spring Boot 4.0.6 (OpenTelemetry Spring Boot starter (2.27.0) has been startedappears in the log,OpenTelemetryAppenderinstalls and ships logs to the configured OTLP endpoint), but nospan.kind=serverspans 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: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=trueand=falseon 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 HTTPGET) 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
spring-boot-starter-web(Tomcat 11, Spring Framework 7.0.7, Spring Web MVC 7 on the classpath).opentelemetry-spring-boot-starter:2.27.0(viaopentelemetry-instrumentation-bom:2.27.0).otel.exporter.otlp.endpoint=…,otel.sdk.disabled=false,otel.traces.exporter=otlp.Expected Result
Each incoming HTTP request produces a
span.kind=serverspan, the way SB3.x + starter 2.17.x does today.Actual Result
Zero
span.kind=serverspans. The starter ships onlyopentelemetry-spring-webmvc-6.0:2.27.0-alphaand there is nospring-webmvc-7.0artifact 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.importsinopentelemetry-spring-boot-autoconfigure-2.27.0.jarconfirms aSpringWebMvc6InstrumentationAutoConfigurationbut noWebMvc7peer: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:
WebMvc6 vs WebMvc7 module. The
instrumentation/spring/spring-webmvc/spring-webmvc-6.0/module's filter —WebMvcTelemetry/OpenTelemetryHandlerInterceptor— is instantiated bySpringWebMvc6InstrumentationAutoConfiguration. 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'sDispatcherServlet/HandlerExecutionChain/HandlerInterceptorinternals diverged enough that theOpenTelemetryHandlerInterceptordoesn't end up wired into the chain, OR theFilterbean 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.0peer module (mirroring the existing 6.0 one) with a proper smoke test againstspring-boot-starter-parent:4.0.xwould close this gap. Alternatively, if Spring 7's WebMvc internals are still compatible at the public-API level, the existing 6.0 module + a newSpringWebMvc7InstrumentationAutoConfigurationthat registers the filter at a defensible order (Ordered.HIGHEST_PRECEDENCE + 1or similar, before Spring Security's chain) might be enough.AOT-friendliness. Two related things bite once
spring.aot.enabled=trueis in play (which is increasingly the default for SB4 deployments):@ConditionalOnEnabledInstrumentation→InstrumentationPropertyEnabledresolvesotel.instrumentation.<module>.enabledagainst the Environment at AOT processing time, when no profile is active and onlyapplication.propertiesis loaded. If a user has the property set inapplication-<profile>.properties, AOT misses it. We had to pass-Dspring.profiles.active=<our gcp profile>toprocess-aotto get a sane factory. Worth either documenting this gotcha in the starter README, or changing the Conditions to defer to runtime evaluation (Spring AOT supportsConditionthat opts out of AOT-time resolution by implementingAotApplicationContextInitializeror returningConditionOutcomethat indicates "uncertain").OtelDisabled/OtelEnabled(ininstrumentation/spring/spring-boot-autoconfigure/.../internal/) readotel.sdk.disabledthe same way. Same fix surface as above.Filter/HandlerInterceptorbean registration viaFilterRegistrationBeanshould already be AOT-tracked by Spring Boot'sRegisterReflectionForBindingmachinery, but worth double-checking under Spring AOT in a smoke test withspring.aot.enabled=trueset for theprocess-aotMaven goal AND the runtime JVM.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 aspan.kind=serverspan 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-bom2.27.0 (latest stable, April 21 2026 release)opentelemetry-spring-boot-starter2.27.0opentelemetry-spring-webmvc-6.02.27.0-alpha (transitive)Spring Versions
spring-boot-starter-parent4.0.6spring-context/spring-web/spring-webmvc/spring-core7.0.7