After upgrading from 3.1.5 to 3.2.0 my application fails:

org.springframework.context.ApplicationContextException: Failed to start bean 'simpleBrokerMessageHandler'

The reason:

Caused by: java.lang.NullPointerException: Cannot invoke "java.util.concurrent.ScheduledExecutorService.scheduleWithFixedDelay(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)" because "this.scheduledExecutor" is null
        at org.springframework.scheduling.concurrent.ConcurrentTaskScheduler.scheduleWithFixedDelay(ConcurrentTaskScheduler.java:262) ~[spring-context-6.1.1.jar:6.1.1]
        at org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler.startInternal(SimpleBrokerMessageHandler.java:271) ~[spring-messaging-6.1.1.jar:6.1.1]
        at org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler.start(AbstractBrokerMessageHandler.java:210) ~[spring-messaging-6.1.1.jar:6.1.1]
        at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:284) ~[spring-context-6.1.1.jar:6.1.1]

Can you please help me to fix it?

Comment From: philwebb

Can you please help me to fix it?

Not without more information I'm afraid. Could you please provide a sample application that reproduces the problem.

Comment From: szolen

I had the same problem with version 3.2.0. I suspect that the problem is caused by the websocket (MessageBroker). If I comment the following lines in the WebSocketMessageBrokerConfigurer implementation, the problem is solved.

@Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/public", "/private").setHeartbeatValue(new long[] { HEART_BEAT, HEART_BEAT }).setTaskScheduler(new DefaultManagedTaskScheduler());
        config.setUserDestinationPrefix("/user");
    }

In my case, this is shown in the log:

[2023.12.01 10:57:12] [ERROR] [SpringApplication.java:839] Application run failed
org.springframework.context.ApplicationContextException: Failed to start bean 'simpleBrokerMessageHandler'
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:287)
    at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:467)
    at java.base/java.lang.Iterable.forEach(Iterable.java:75)
    at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:256)
    at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:201)
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:965)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:619)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:455)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:323)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1342)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1331)
    at hu.prolan.prorisdiag.psb.webservice.PSBDiagWebService.main(PSBDiagWebService.java:20)
Caused by: java.lang.NullPointerException: Cannot invoke "java.util.concurrent.ScheduledExecutorService.scheduleWithFixedDelay(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)" because "this.scheduledExecutor" is null
    at org.springframework.scheduling.concurrent.ConcurrentTaskScheduler.scheduleWithFixedDelay(ConcurrentTaskScheduler.java:262)
    at org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler.startInternal(SimpleBrokerMessageHandler.java:271)
    at org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler.start(AbstractBrokerMessageHandler.java:210)
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:284)
    ... 13 common frames omitted

Comment From: szolen

The problem in my case is solved by not setting the DefaultManagedTaskScheduler in the MessageBrokerRegistry, but by registering a custom TaskScheduler:

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/public", "/private")
    .setHeartbeatValue(new long[] { HEART_BEAT, HEART_BEAT })
    .setTaskScheduler(heartBeatScheduler());

    config.setUserDestinationPrefix("/user");
}

@Bean
public TaskScheduler heartBeatScheduler() {
    return new ThreadPoolTaskScheduler();
}

Comment From: fullopt

This helped, thanks!

Comment From: philwebb

I think this might be a result of https://github.com/spring-projects/spring-framework/issues/27914.

The following code is enough to trigger the exception.

DefaultManagedTaskScheduler scheduler = new DefaultManagedTaskScheduler();
scheduler.scheduleWithFixedDelay(() -> {}, Duration.ofSeconds(1));

I'm not sure if this is a bug or not. The same code with the previous version of Spring would create a ScheduledExecutorService but it wouldn't be from the JNDI context because afterPropertiesSet wasn't ever called.

Comment From: philwebb

@bclozel Could you please transfer this to the Framework issue tracker so that someone from that team can make an assessment?

Comment From: snicoll

The following code is enough to trigger the exception.

Correct but it's a completely different implementation hierarchy. ConcurrentTaskScheduler has its default constructor deprecated and, using it anyway works as expected as it still initializes the service. We'll have to debug how simpleBrokerMessageHandler is creating a scheduler in such a state.

Comment From: snicoll

@fullopt can you confirm you're also using DefaultManagedTaskScheduler ? As far as I can see, that only happens if you opt-in for that scheduler and you don't create it as a managed object (bean).

Comment From: fullopt

@fullopt can you confirm you're also using DefaultManagedTaskScheduler ? As far as I can see, that only happens if you opt-in for that scheduler and you don't create it as a managed object (bean).

Exactly, I used DefaultManagedTaskScheduler() what caused the issue. So far I changed for bean option proposed by @szolen and it works as expected.

Comment From: snicoll

@szolen and @fullopt for the record DefaultManagedTaskScheduler should be used if you want to lookup the executor via JNDI. We're wondering if you've used this class thinking that it was the "default scheduler" given its name, can you let us know? If you used it thinking it was the default scheduler, ThreadPoolTaskScheduler is probably what you should be using.

As for creating the scheduler as a bean, this should have been done in the first place as the context needs that in order to honor the lifecycle, both during the startup phase, and the shutdown phase. Creating such a class manually is not the expected behavior.

As such the use cases above will still throw an exception, a different one though now.

Comment From: szolen

@szolen and @fullopt for the record DefaultManagedTaskScheduler should be used if you want to lookup the executor via JNDI. We're wondering if you've used this class thinking that it was the "default scheduler" given its name, can you let us know?

As for creating the scheduler as a bean, this should have been done in the first place as the context needs that in order to honor the lifecycle, both during the startup phase, and the shutdown phase. Creating such a class manually is not the expected behavior.

As such the use cases above will still throw an exception, a different one though now.

Yes, I thought from the name that this was the default scheduler.

However, on the configuration class I used an @Order annotation: @Order(Ordered.HIGHEST_PRECEDENCE) but it seems that was insufficient.