mnd
mnd

Reputation: 2789

Spring 6 application can't start when setting heartbeat and task scheduler for WebSockets

In upgrading a Spring Boot 2 application to Spring Boot 3 (Spring 5 to Spring 6) it fails to start due to WebSocket configuration.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // ---------------------------------
        // This works in Spring Boot 2 and 3
        // ---------------------------------
        config.enableSimpleBroker("/topic");

        // -------------------------------------------------------
        // This works in Spring Boot 2, but fails in Spring Boot 3
        // -------------------------------------------------------
        config.enableSimpleBroker("/topic")
                .setHeartbeatValue(new long[] { 10000, 10000 })
                .setTaskScheduler(new DefaultManagedTaskScheduler());
        
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket");
    }
}

Trying to set the heartbeat and task scheduler fails with the following information in Spring Boot 3 (Spring 6).

INFO 19002 --- [demo] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
INFO 19002 --- [demo] [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : Starting...
INFO 19002 --- [demo] [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : BrokerAvailabilityEvent[available=true, SimpleBrokerMessageHandler [org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry@49a6f486]]
WARN 19002 --- [demo] [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'simpleBrokerMessageHandler'
INFO 19002 --- [demo] [           main] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
ERROR 19002 --- [demo] [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Failed to start bean 'simpleBrokerMessageHandler'
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:288) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:469) ~[spring-context-6.1.11.jar:6.1.11]
    at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
    at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:257) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:202) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:990) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:628) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.2.jar:3.3.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.2.jar:3.3.2]
    at com.example.demo.DemoApplication.main(DemoApplication.java:10) ~[classes/:na]
Caused by: java.lang.IllegalStateException: No ScheduledExecutor is configured
    at org.springframework.scheduling.concurrent.ConcurrentTaskScheduler.getScheduledExecutor(ConcurrentTaskScheduler.java:174) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.scheduling.concurrent.ConcurrentTaskScheduler.scheduleWithFixedDelay(ConcurrentTaskScheduler.java:275) ~[spring-context-6.1.11.jar:6.1.11]
    at org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler.startInternal(SimpleBrokerMessageHandler.java:271) ~[spring-messaging-6.1.11.jar:6.1.11]
    at org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler.start(AbstractBrokerMessageHandler.java:227) ~[spring-messaging-6.1.11.jar:6.1.11]
    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:285) ~[spring-context-6.1.11.jar:6.1.11]
    ... 13 common frames omitted

Any ideas how to fix this? Specifically how to set heartbeat values for WebSockets in Spring Boot 3 (Spring 6)?

Upvotes: 0

Views: 182

Answers (1)

mnd
mnd

Reputation: 2789

Thanks to @JorgeCampos for suggestions that helped move me to the correct answer.

The problem was using DefaultManagedTaskScheduler as the task scheduler. According to Stephane Nicoll from the Spring Boot team:

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.

Source

According to the GitHub issue, it appears this changed from Spring Boot 3.1.5 to 3.2.0. I'm now using the following to get WebSockets working in Spring Boot 3.3.2. Specifically creating a custom ThreadPoolTaskScheduler bean that gets included as the task scheduler.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic")
                .setHeartbeatValue(new long[] { 10000, 10000 })
                .setTaskScheduler(webSocketMessageBrokerTaskScheduler());
        
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket");
    }

    @Bean
    public ThreadPoolTaskScheduler webSocketMessageBrokerTaskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(1);
        taskScheduler.setThreadNamePrefix("wss-heartbeat-thread-");
        taskScheduler.initialize();
        return taskScheduler;
    }
}

Upvotes: 1

Related Questions