Affects: 6.1 Context: Upgrading from Spring Boot 3.1 (Spring 6.0) to Spring Boot 3.2 (Spring 6.1)
We encountered an issue with Beans defined as anonymous objects containing a single method annotated with @Scheduled
. For instance:
@Bean
fun somethingCoolStreamConsumerScheduler(
coolStreamConsumer: CoolStreamConsumer,
coolnessChecker: CoolnessChecker,
) = object {
@Scheduled(fixedDelayString = "\${kafka.consumers.coolStuff.fixedDelay}")
fun execute() = coolStreamConsumer.consume()
}
Upon execution, these tasks failed to initialize, resulting in the following stack trace:
java.lang.NullPointerException: null
at java.base/java.util.Objects.requireNonNull(Objects.java:209) ~[?:?]
at io.micrometer.common.ImmutableKeyValue.<init>(ImmutableKeyValue.java:38) ~[micrometer-commons-1.12.2-SNAPSHOT.jar:1.12.2-SNAPSHOT]
at io.micrometer.common.KeyValue.of(KeyValue.java:48) ~[micrometer-commons-1.12.2-SNAPSHOT.jar:1.12.2-SNAPSHOT]
at io.micrometer.common.KeyValue.of(KeyValue.java:58) ~[micrometer-commons-1.12.2-SNAPSHOT.jar:1.12.2-SNAPSHOT]
at org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention.codeNamespace(DefaultScheduledTaskObservationConvention.java:64) ~[spring-context-6.1.2.jar:6.1.2]
at org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention.getLowCardinalityKeyValues(DefaultScheduledTaskObservationConvention.java:56) ~[spring-context-6.1.2.jar:6.1.2]
at org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention.getLowCardinalityKeyValues(DefaultScheduledTaskObservationConvention.java:31) ~[spring-context-6.1.2.jar:6.1.2]
at io.micrometer.observation.SimpleObservation.start(SimpleObservation.java:152) ~[micrometer-observation-1.12.2-SNAPSHOT.jar:1.12.2-SNAPSHOT]
at io.micrometer.observation.Observation.observe(Observation.java:497) ~[micrometer-observation-1.12.2-SNAPSHOT.jar:1.12.2-SNAPSHOT]
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124) ~[spring-context-6.1.2.jar:6.1.2]
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) [spring-context-6.1.2.jar:6.1.2]
After investigating and debugging, we found that in this Spring version...
@Scheduled methods are now instrumented for observability.
Our issue arises because micrometer SimpleObservation.start
requires the class canonicalName
, which returns null
for our anonymous objects.
We resolved this by using named classes. However, I want to highlight this case as in our case a MINOR
update has meant a lot of changes in our code base, and there does not seem to be a way to disable this, now default, behaviour in Spring 6.1.
Looking forward to your feedback.
Comment From: tomacco
Update, ok I've seen you have already corrected this behaviour
protected KeyValue codeNamespace(ScheduledTaskObservationContext context) {
if (context.getTargetClass().getCanonicalName() != null) {
return KeyValue.of(LowCardinalityKeyNames.CODE_NAMESPACE, context.getTargetClass().getCanonicalName());
}
return CODE_NAMESPACE_ANONYMOUS;
}
Closing the issue.
Comment From: bclozel
As you've found out, this is a duplicate of https://github.com/spring-projects/spring-framework/issues/31918, which is already solved in 6.1.3-SNAPSHOT.
We resolved this by using named classes. However, I want to highlight this case as in our case a MINOR update has meant a lot of changes in our code base, and there does not seem to be a way to disable this, now default, behaviour in Spring 6.1.
I think you can disable this with an observation predicate in the meantime:
import io.micrometer.observation.ObservationPredicate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class ObservationConfig {
@Bean
public ObservationPredicate disableScheduledObservation() {
return (name, context) -> "tasks.scheduled.execution".equals(name);
}
}
Care to elaborate about the changes in your codebase? Usually minor Spring Framework releases come with new features and few deprecations and required code changes. I'd be curious to know which Framework changes caused pain. Now about this particular change, this is a bug in a new feature that was not caught during the milestone phase.
Comment From: tomacco
@bclozel as we didn't know how to disable this, our first approach was to create normal classes so the class.getCanonicalName()
won't return null
. It required more work than expected basically because we have several methods annotated with this annotation.
Certainly your suggestion is way better. Finally what we are going to do is to wait until this is part of Spring Boot 3.
Thank you for your time.