This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Boot 3.4.4! |
Task Execution and Scheduling
In the absence of an Executor
bean in the context, Spring Boot auto-configures an AsyncTaskExecutor
.
When virtual threads are enabled (using Java 21+ and spring.threads.virtual.enabled
set to true
) this will be a SimpleAsyncTaskExecutor
that uses virtual threads.
Otherwise, it will be a ThreadPoolTaskExecutor
with sensible defaults.
The auto-configured AsyncTaskExecutor
is used for the following integrations unless a custom Executor
bean is defined:
-
Execution of asynchronous tasks using
@EnableAsync
, unless a bean of typeAsyncConfigurer
is defined. -
Asynchronous handling of
Callable
return values from controller methods in Spring for GraphQL. -
Asynchronous request handling in Spring MVC.
-
Support for blocking execution in Spring WebFlux.
-
Utilized for inbound and outbound message channels in Spring WebSocket.
-
Acts as a bootstrap executor for JPA, based on the bootstrap mode of JPA repositories.
While this approach works in most scenarios, Spring Boot allows you to override the auto-configured AsyncTaskExecutor
.
By default, when a custom Executor
bean is registered, the auto-configured AsyncTaskExecutor
backs off, and the custom Executor
is used for regular task execution (via @EnableAsync
).
However, Spring MVC, Spring WebFlux, and Spring GraphQL all require a bean named applicationTaskExecutor
.
For Spring MVC and Spring WebFlux, this bean must be of type AsyncTaskExecutor
, whereas Spring GraphQL does not enforce this type requirement.
Spring WebSocket and JPA will use AsyncTaskExecutor
if either a single bean of this type is available or a bean named applicationTaskExecutor
is defined.
The following code snippet demonstrates how to register a custom AsyncTaskExecutor
to be used with Spring MVC, Spring WebFlux, Spring GraphQL, Spring WebSocket and JPA.
-
Java
-
Kotlin
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
@Configuration(proxyBeanMethods = false)
public class MyTaskExecutorConfiguration {
@Bean("applicationTaskExecutor")
SimpleAsyncTaskExecutor applicationTaskExecutor() {
return new SimpleAsyncTaskExecutor("app-");
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.task.SimpleAsyncTaskExecutor
@Configuration(proxyBeanMethods = false)
class MyTaskExecutorConfiguration {
@Bean("applicationTaskExecutor")
fun applicationTaskExecutor(): SimpleAsyncTaskExecutor {
return SimpleAsyncTaskExecutor("app-")
}
}
The |
If neither the auto-configured |
If your application needs multiple Executor
beans for different integrations, such as one for regular task execution with @EnableAsync
and other for Spring MVC, Spring WebFlux, Spring WebSocket and JPA, you can configure them as follows.
-
Java
-
Kotlin
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration(proxyBeanMethods = false)
public class MyTaskExecutorConfiguration {
@Bean("applicationTaskExecutor")
SimpleAsyncTaskExecutor applicationTaskExecutor() {
return new SimpleAsyncTaskExecutor("app-");
}
@Bean("taskExecutor")
ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("async-");
return threadPoolTaskExecutor;
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.task.SimpleAsyncTaskExecutor
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
@Configuration(proxyBeanMethods = false)
class MyTaskExecutorConfiguration {
@Bean("applicationTaskExecutor")
fun applicationTaskExecutor(): SimpleAsyncTaskExecutor {
return SimpleAsyncTaskExecutor("app-")
}
@Bean("taskExecutor")
fun taskExecutor(): ThreadPoolTaskExecutor {
val threadPoolTaskExecutor = ThreadPoolTaskExecutor()
threadPoolTaskExecutor.threadNamePrefix = "async-"
return threadPoolTaskExecutor
}
}
The auto-configured
|
If a taskExecutor
named bean is not an option, you can mark your bean as @Primary
or define an AsyncConfigurer
bean to specify the Executor
responsible for handling regular task execution with @EnableAsync
.
The following example demonstrates how to achieve this.
-
Java
-
Kotlin
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
@Configuration(proxyBeanMethods = false)
public class MyTaskExecutorConfiguration {
@Bean
AsyncConfigurer asyncConfigurer(ExecutorService executorService) {
return new AsyncConfigurer() {
@Override
public Executor getAsyncExecutor() {
return executorService;
}
};
}
@Bean
ExecutorService executorService() {
return Executors.newCachedThreadPool();
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.annotation.AsyncConfigurer
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@Configuration(proxyBeanMethods = false)
class MyTaskExecutorConfiguration {
@Bean
fun asyncConfigurer(executorService: ExecutorService): AsyncConfigurer {
return object : AsyncConfigurer {
override fun getAsyncExecutor(): Executor {
return executorService
}
}
}
@Bean
fun executorService(): ExecutorService {
return Executors.newCachedThreadPool()
}
}
To register a custom Executor
while keeping the auto-configured AsyncTaskExecutor
, you can create a custom Executor
bean and set the defaultCandidate=false
attribute in its @Bean
annotation, as demonstrated in the following example:
-
Java
-
Kotlin
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyTaskExecutorConfiguration {
@Bean(defaultCandidate = false)
@Qualifier("scheduledExecutorService")
ScheduledExecutorService scheduledExecutorService() {
return Executors.newSingleThreadScheduledExecutor();
}
}
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
@Configuration(proxyBeanMethods = false)
class MyTaskExecutorConfiguration {
@Bean(defaultCandidate = false)
@Qualifier("scheduledExecutorService")
fun scheduledExecutorService(): ScheduledExecutorService {
return Executors.newSingleThreadScheduledExecutor()
}
}
In that case, you will be able to autowire your custom Executor
into other components while retaining the auto-configured AsyncTaskExecutor
.
However, remember to use the @Qualifier
annotation alongside @Autowired
.
If this is not possible for you, you can request Spring Boot to auto-configure an AsyncTaskExecutor
anyway, as follows:
-
Properties
-
YAML
spring.task.execution.mode=force
spring:
task:
execution:
mode: force
The auto-configured AsyncTaskExecutor
will be used automatically for all integrations, even if a custom Executor
bean is registered, including those marked as @Primary
.
These integrations include:
-
Asynchronous task execution (
@EnableAsync
), unless anAsyncConfigurer
bean is present. -
Spring for GraphQL’s asynchronous handling of
Callable
return values from controller methods. -
Spring MVC’s asynchronous request processing.
-
Spring WebFlux’s blocking execution support.
-
Utilized for inbound and outbound message channels in Spring WebSocket.
-
Acts as a bootstrap executor for JPA, based on the bootstrap mode of JPA repositories.
Depending on your target arrangement, you could set |
When |
When a ThreadPoolTaskExecutor
is auto-configured, the thread pool uses 8 core threads that can grow and shrink according to the load.
Those default settings can be fine-tuned using the spring.task.execution
namespace, as shown in the following example:
-
Properties
-
YAML
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
spring:
task:
execution:
pool:
max-size: 16
queue-capacity: 100
keep-alive: "10s"
This changes the thread pool to use a bounded queue so that when the queue is full (100 tasks), the thread pool increases to maximum 16 threads. Shrinking of the pool is more aggressive as threads are reclaimed when they are idle for 10 seconds (rather than 60 seconds by default).
A scheduler can also be auto-configured if it needs to be associated with scheduled task execution (using @EnableScheduling
for instance).
If virtual threads are enabled (using Java 21+ and spring.threads.virtual.enabled
set to true
) this will be a SimpleAsyncTaskScheduler
that uses virtual threads.
This SimpleAsyncTaskScheduler
will ignore any pooling related properties.
If virtual threads are not enabled, it will be a ThreadPoolTaskScheduler
with sensible defaults.
The ThreadPoolTaskScheduler
uses one thread by default and its settings can be fine-tuned using the spring.task.scheduling
namespace, as shown in the following example:
-
Properties
-
YAML
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
spring:
task:
scheduling:
thread-name-prefix: "scheduling-"
pool:
size: 2
A ThreadPoolTaskExecutorBuilder
bean, a SimpleAsyncTaskExecutorBuilder
bean, a ThreadPoolTaskSchedulerBuilder
bean and a SimpleAsyncTaskSchedulerBuilder
are made available in the context if a custom executor or scheduler needs to be created.
The SimpleAsyncTaskExecutorBuilder
and SimpleAsyncTaskSchedulerBuilder
beans are auto-configured to use virtual threads if they are enabled (using Java 21+ and spring.threads.virtual.enabled
set to true
).