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 typeAsyncConfigureris defined. -
Asynchronous handling of
Callablereturn 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.
-
Bootstrap executor for JPA, based on the bootstrap mode of JPA repositories.
-
Bootstrap executor for background initialization of beans in the
ApplicationContext.
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.
Finally, the boostrap executor of the ApplicationContext uses a bean named applicationTaskExecutor unless a bean named bootstrapExecutor 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, JPA, and background initialization of beans.
-
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.setThreadNamePrefix("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 anAsyncConfigurerbean is present. -
Spring for GraphQL’s asynchronous handling of
Callablereturn 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.
-
Bootstrap executor for JPA, based on the bootstrap mode of JPA repositories.
-
Bootstrap executor for background initialization of beans in the
ApplicationContext, unless a bean namedbootstrapExecutoris defined.
|
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).