Skip to content

Commit deeaa48

Browse files
committed
Update documentation for Task Execution
Signed-off-by: Dmytro Nosan <[email protected]>
1 parent faef7d5 commit deeaa48

File tree

2 files changed

+178
-12
lines changed

2 files changed

+178
-12
lines changed

spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/task-execution-and-scheduling.adoc

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,78 @@ In the absence of an javadoc:java.util.concurrent.Executor[] bean in the context
55
When virtual threads are enabled (using Java 21+ and configprop:spring.threads.virtual.enabled[] set to `true`) this will be a javadoc:org.springframework.core.task.SimpleAsyncTaskExecutor[] that uses virtual threads.
66
Otherwise, it will be a javadoc:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor[] with sensible defaults.
77

8-
If a custom `Executor` bean is present, you can request Spring Boot to auto-configure an `AsyncTaskExecutor` anyway, as follows:
8+
The auto-configured javadoc:org.springframework.core.task.AsyncTaskExecutor[] is used for
9+
the following integrations unless a custom javadoc:java.util.concurrent.Executor[] bean is defined:
10+
11+
- Execution of asynchronous tasks using `@EnableAsync`, unless a bean of type javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] is defined.
12+
- Asynchronous handling of javadoc:java.util.concurrent.Callable[] return values from controller methods in Spring for GraphQL.
13+
- Asynchronous request handling in Spring MVC.
14+
- Support for blocking execution in Spring WebFlux.
15+
16+
While this approach works in most scenarios, Spring Boot allows you to override the auto-configured
17+
javadoc:org.springframework.core.task.AsyncTaskExecutor[]. By default, when a custom
18+
javadoc:java.util.concurrent.Executor[] bean is registered, the auto-configured
19+
javadoc:org.springframework.core.task.AsyncTaskExecutor[] steps aside, and the custom
20+
javadoc:java.util.concurrent.Executor[] is used for regular task execution (via `@EnableAsync`).
21+
However, Spring MVC, Spring WebFlux, and Spring GraphQL all require a bean named `applicationTaskExecutor`.
22+
For Spring MVC and Spring WebFlux, this bean must be of type javadoc:org.springframework.core.task.AsyncTaskExecutor[],
23+
whereas Spring GraphQL does not enforce this type requirement.
24+
25+
The following code snippet demonstrates how to register a custom javadoc:org.springframework.core.task.AsyncTaskExecutor[]
26+
to be used with Spring MVC, Spring WebFlux, Spring GraphQL:
27+
28+
include-code::TaskExecutionConfigurationExamples[tag=application-task-executor]
29+
30+
[NOTE]
31+
====
32+
The `applicationTaskExecutor` bean will also be used for regular task execution if there is no
33+
`@Primary` bean or a bean named `taskExecutor` of type javadoc:java.util.concurrent.Executor[]
34+
or javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] present in the application context.
35+
====
36+
37+
[WARNING]
38+
====
39+
If neither the auto-configured `AsyncTaskExecutor` nor the `applicationTaskExecutor` bean is defined,
40+
the application defaults to a bean named `taskExecutor` for regular task execution (`@EnableAsync`),
41+
following Spring Framework's behavior. However, this bean will not be used for Spring MVC, Spring WebFlux, or Spring GraphQL.
42+
====
43+
44+
If your application needs multiple `Executor` beans for different integrations,
45+
such as one for regular task execution with `@EnableAsync` and other for Spring MVC, Spring WebFlux, and Spring GraphQL,
46+
you can configure them as follows:
47+
48+
include-code::TaskExecutionConfigurationExamples[tag=multiple-task-executor]
49+
50+
[TIP]
51+
====
52+
The auto-configured javadoc:org.springframework.boot.task.ThreadPoolTaskExecutorBuilder[] or
53+
javadoc:org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder[] allow you to easily create instances
54+
of type javadoc:org.springframework.core.task.AsyncTaskExecutor[] that replicate the default behavior of auto-configuration.
55+
56+
include-code::TaskExecutionConfigurationExamples[tag=executor-builder]
57+
====
58+
59+
If a `taskExecutor` named bean is not an option, you can mark your bean as `@Primary` or define an
60+
javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean to specify the
61+
`Executor` responsible for handling regular task execution with `@EnableAsync`.
62+
The following example demonstrates how to achieve this:
63+
64+
include-code::TaskExecutionConfigurationExamples[tag=async-configurer]
65+
66+
To register a custom javadoc:java.util.concurrent.Executor[] while keeping the auto-configured
67+
javadoc:org.springframework.core.task.AsyncTaskExecutor[], you can create a custom
68+
javadoc:java.util.concurrent.Executor[] bean and set the `defaultCandidate=false` attribute in its
69+
javadoc:org.springframework.context.annotation.Bean[format=annotation] annotation,
70+
as demonstrated in the following example:
71+
72+
include-code::TaskExecutionConfigurationExamples[tag=default-candidate-task-executor]
73+
74+
In that case, you will be able to javadoc:org.springframework.beans.factory.annotation.Autowired[format=annotation]
75+
your custom javadoc:java.util.concurrent.Executor[] into other components while retaining the auto-configured `AsyncTaskExecutor`.
76+
However, remember to use the javadoc:org.springframework.beans.factory.annotation.Qualifier[format=annotation] annotation
77+
alongside javadoc:org.springframework.beans.factory.annotation.Autowired[format=annotation] .
78+
79+
If for some reason, it is not possible, you can request Spring Boot to auto-configure an `AsyncTaskExecutor` anyway, as follows:
980

1081
[configprops,yaml]
1182
----
@@ -15,7 +86,9 @@ spring:
1586
mode: force
1687
----
1788

18-
The auto-configured executor will be automatically used for:
89+
The auto-configured javadoc:org.springframework.core.task.AsyncTaskExecutor[] will be used automatically
90+
for all integrations, even if a custom javadoc:java.util.concurrent.Executor[] bean is registered,
91+
including those marked as `@Primary`. These integrations include:
1992

2093
- Asynchronous task execution (`@EnableAsync`), unless an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean is present.
2194
- Spring for GraphQL's asynchronous handling of javadoc:java.util.concurrent.Callable[] return values from controller methods.
@@ -24,22 +97,17 @@ The auto-configured executor will be automatically used for:
2497

2598
[TIP]
2699
====
27-
If you have defined a custom javadoc:java.util.concurrent.Executor[] in the context, both regular task execution (that is javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]) and Spring for GraphQL will use it.
28-
However, the Spring MVC and Spring WebFlux support will only use it if it is an javadoc:org.springframework.core.task.AsyncTaskExecutor[] implementation named `applicationTaskExecutor`.
29-
30100
Depending on your target arrangement, you could set configprop:spring.task.execution.mode[] to `force` to auto-configure an `applicationTaskExecutor`, change your javadoc:java.util.concurrent.Executor[] into an javadoc:org.springframework.core.task.AsyncTaskExecutor[] or define both an javadoc:org.springframework.core.task.AsyncTaskExecutor[] and an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] wrapping your custom javadoc:java.util.concurrent.Executor[].
31-
32-
Another option is to define those beans explicitly.
33-
The auto-configured javadoc:org.springframework.boot.task.ThreadPoolTaskExecutorBuilder[] or javadoc:org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder[] allow you to easily create instances that reproduce what the auto-configuration does by default.
34101
====
35102

36-
[NOTE]
103+
[WARNING]
37104
====
38-
If multiple javadoc:java.util.concurrent.Executor[] beans are defined with configprop:spring.task.execution.mode[] to `force`, all the supported integrations look for a bean named `applicationTaskExecutor`.
39-
If the auto-configured `AsyncTaskExecutor` is not defined, only regular task execution fallbacks to a bean named `taskExecutor` to match Spring Framework's behavior.
105+
When `force` mode is enabled, `applicationTaskExecutor` will also be configured for regular
106+
task execution with `@EnableAsync`, even if a `@Primary` bean or a bean named `taskExecutor`
107+
of type javadoc:java.util.concurrent.Executor[] is present. The only way to override the `Executor`
108+
for regular tasks is by registering an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean.
40109
====
41110

42-
43111
When a javadoc:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor[] is auto-configured, the thread pool uses 8 core threads that can grow and shrink according to the load.
44112
Those default settings can be fine-tuned using the `spring.task.execution` namespace, as shown in the following example:
45113

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.features.taskexecutionandscheduling;
18+
19+
import java.util.concurrent.Executor;
20+
import java.util.concurrent.ExecutorService;
21+
import java.util.concurrent.Executors;
22+
import java.util.concurrent.ScheduledExecutorService;
23+
24+
import org.springframework.beans.factory.annotation.Qualifier;
25+
import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.core.task.SimpleAsyncTaskExecutor;
29+
import org.springframework.scheduling.annotation.AsyncConfigurer;
30+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
31+
32+
public class TaskExecutionConfigurationExamples {
33+
34+
// tag::default-candidate-task-executor[]
35+
@Bean(defaultCandidate = false)
36+
@Qualifier("scheduledExecutorService")
37+
ScheduledExecutorService scheduledExecutorService() {
38+
return Executors.newSingleThreadScheduledExecutor();
39+
}
40+
// end::default-candidate-task-executor[]
41+
42+
// tag::application-task-executor[]
43+
@Bean("applicationTaskExecutor")
44+
SimpleAsyncTaskExecutor applicationTaskExecutor() {
45+
return new SimpleAsyncTaskExecutor("app-");
46+
}
47+
// end::application-task-executor[]
48+
49+
// tag::executor-builder[]
50+
@Bean
51+
SimpleAsyncTaskExecutor taskExecutor(SimpleAsyncTaskExecutorBuilder builder) {
52+
return builder.build();
53+
}
54+
// end::executor-builder[]
55+
56+
static class MultipleTaskExecutor {
57+
58+
// tag::multiple-task-executor[]
59+
@Bean("applicationTaskExecutor")
60+
SimpleAsyncTaskExecutor applicationTaskExecutor() {
61+
return new SimpleAsyncTaskExecutor("app-");
62+
}
63+
64+
@Bean("taskExecutor")
65+
ThreadPoolTaskExecutor taskExecutor() {
66+
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
67+
threadPoolTaskExecutor.setThreadNamePrefix("async-");
68+
return threadPoolTaskExecutor;
69+
}
70+
// end::multiple-task-executor[]
71+
72+
}
73+
74+
// tag::async-configurer[]
75+
@Configuration(proxyBeanMethods = false)
76+
public class TaskExecutionConfiguration {
77+
78+
@Bean
79+
AsyncConfigurer asyncConfigurer(ExecutorService executorService) {
80+
return new AsyncConfigurer() {
81+
82+
@Override
83+
public Executor getAsyncExecutor() {
84+
return executorService;
85+
}
86+
87+
};
88+
}
89+
90+
@Bean
91+
ExecutorService executorService() {
92+
return Executors.newCachedThreadPool();
93+
}
94+
95+
}
96+
// end::async-configurer[]
97+
98+
}

0 commit comments

Comments
 (0)