From 9eb03d13fb032ed2d530fda82e52d90300e3436b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Wed, 15 Jul 2026 09:45:44 +0200 Subject: [PATCH] Configure virtual threads on RedisMessageListenerContainer Closes gh-50884 --- ...ataRedisAnnotationDrivenConfiguration.java | 21 ++++++++++++ ...disAnnotationDrivenConfigurationTests.java | 34 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/DataRedisAnnotationDrivenConfiguration.java b/module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/DataRedisAnnotationDrivenConfiguration.java index bfff28513ab..471a1a32f87 100644 --- a/module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/DataRedisAnnotationDrivenConfiguration.java +++ b/module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/DataRedisAnnotationDrivenConfiguration.java @@ -19,8 +19,11 @@ package org.springframework.boot.data.redis.autoconfigure; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; +import org.springframework.boot.thread.Threading; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.VirtualThreadTaskExecutor; import org.springframework.data.redis.annotation.EnableRedisListeners; import org.springframework.data.redis.config.RedisListenerConfigUtils; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -53,8 +56,26 @@ class DataRedisAnnotationDrivenConfiguration { @Bean(name = DEFAULT_MESSAGE_LISTENER_BEAN_NAME) @ConditionalOnSingleCandidate(RedisConnectionFactory.class) @ConditionalOnMissingBean(name = DEFAULT_MESSAGE_LISTENER_BEAN_NAME) + @ConditionalOnThreading(Threading.PLATFORM) RedisMessageListenerContainer redisMessageListenerContainer(RedisMessageListenerContainerConfigurer configurer, RedisConnectionFactory redisConnectionFactory) { + return defaultMessageListenerContainer(configurer, redisConnectionFactory); + } + + @Bean(name = DEFAULT_MESSAGE_LISTENER_BEAN_NAME) + @ConditionalOnSingleCandidate(RedisConnectionFactory.class) + @ConditionalOnMissingBean(name = DEFAULT_MESSAGE_LISTENER_BEAN_NAME) + @ConditionalOnThreading(Threading.VIRTUAL) + RedisMessageListenerContainer redisMessageListenerContainerVirtualThreads( + RedisMessageListenerContainerConfigurer configurer, RedisConnectionFactory redisConnectionFactory) { + RedisMessageListenerContainer container = defaultMessageListenerContainer(configurer, redisConnectionFactory); + container + .setTaskExecutor(new VirtualThreadTaskExecutor(RedisMessageListenerContainer.DEFAULT_THREAD_NAME_PREFIX)); + return container; + } + + private static RedisMessageListenerContainer defaultMessageListenerContainer( + RedisMessageListenerContainerConfigurer configurer, RedisConnectionFactory redisConnectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); configurer.configure(container, redisConnectionFactory); return container; diff --git a/module/spring-boot-data-redis/src/test/java/org/springframework/boot/data/redis/autoconfigure/DataRedisAnnotationDrivenConfigurationTests.java b/module/spring-boot-data-redis/src/test/java/org/springframework/boot/data/redis/autoconfigure/DataRedisAnnotationDrivenConfigurationTests.java index 91fbb074855..ee70c152672 100644 --- a/module/spring-boot-data-redis/src/test/java/org/springframework/boot/data/redis/autoconfigure/DataRedisAnnotationDrivenConfigurationTests.java +++ b/module/spring-boot-data-redis/src/test/java/org/springframework/boot/data/redis/autoconfigure/DataRedisAnnotationDrivenConfigurationTests.java @@ -20,6 +20,8 @@ import java.time.Duration; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -27,6 +29,8 @@ import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties.Lis import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.VirtualThreadTaskExecutor; import org.springframework.data.redis.config.RedisListenerConfigUtils; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.listener.RedisMessageListenerContainer; @@ -108,6 +112,36 @@ class DataRedisAnnotationDrivenConfigurationTests { }); } + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void whenVirtualThreadsAreDisableContainerDoesNotUseVirtualThreads() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(RedisMessageListenerContainer.class); + assertThat(context.getBean(RedisMessageListenerContainer.class)).extracting("taskExecutor") + .isInstanceOf(SimpleAsyncTaskExecutor.class); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void whenVirtualThreadsAreEnabledOnJava21AndLaterContainerUsesVirtualThreads() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + assertThat(context).hasSingleBean(RedisMessageListenerContainer.class); + assertThat(context.getBean(RedisMessageListenerContainer.class)).extracting("taskExecutor") + .isInstanceOf(VirtualThreadTaskExecutor.class); + }); + } + + @Test + @EnabledForJreRange(max = JRE.JAVA_20) + void whenVirtualThreadsAreEnabledOnJava20AndEarlierContainerDoesNotUseVirtualThreads() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + assertThat(context).hasSingleBean(RedisMessageListenerContainer.class); + assertThat(context.getBean(RedisMessageListenerContainer.class)).extracting("taskExecutor") + .isInstanceOf(SimpleAsyncTaskExecutor.class); + }); + } + @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(DataRedisProperties.class) static class TestConfiguration {