Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update documentation for Task execution #44926

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,76 @@ In the absence of an javadoc:java.util.concurrent.Executor[] bean in the context
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.
Otherwise, it will be a javadoc:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor[] with sensible defaults.

If a custom `Executor` bean is present, you can request Spring Boot to auto-configure an `AsyncTaskExecutor` anyway, as follows:
The auto-configured javadoc:org.springframework.core.task.AsyncTaskExecutor[] is used for the following integrations unless a custom javadoc:java.util.concurrent.Executor[] bean is defined:

- Execution of asynchronous tasks using javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation], unless a bean of type javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] is defined.
- Asynchronous handling of javadoc:java.util.concurrent.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
javadoc:org.springframework.core.task.AsyncTaskExecutor[].
By default, when a custom javadoc:java.util.concurrent.Executor[] bean is registered, the auto-configured
javadoc:org.springframework.core.task.AsyncTaskExecutor[] steps aside, and the custom javadoc:java.util.concurrent.Executor[] is used for regular task execution (via javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]).

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 javadoc:org.springframework.core.task.AsyncTaskExecutor[], whereas Spring GraphQL does not enforce this type requirement.

Spring WebSocket and JPA will use javadoc:org.springframework.core.task.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 javadoc:org.springframework.core.task.AsyncTaskExecutor[]
to be used with Spring MVC, Spring WebFlux, Spring GraphQL, Spring WebSocket and JPA.

include-code::TaskExecutionConfigurationExamples[tag=application-task-executor]

[NOTE]
====
The `applicationTaskExecutor` bean will also be used for regular task execution if there is no
javadoc:org.springframework.context.annotation.Primary[format=annotation] bean or a bean named `taskExecutor` of type javadoc:java.util.concurrent.Executor[]
or javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] present in the application context.
====

[WARNING]
====
If neither the auto-configured `AsyncTaskExecutor` nor the `applicationTaskExecutor` bean is defined, the application defaults to a bean named `taskExecutor` for regular task execution (javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]), following Spring Framework's behavior.
However, this bean will not be used for Spring MVC, Spring WebFlux, Spring GraphQL.
It could, however, be used for Spring WebSocket or JPA if bean's type is javadoc:org.springframework.core.task.AsyncTaskExecutor[].
====

If your application needs multiple `Executor` beans for different integrations, such as one for regular task execution with javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]
and other for Spring MVC, Spring WebFlux, Spring WebSocket and JPA you can configure them as follows.

include-code::TaskExecutionConfigurationExamples[tag=multiple-task-executor]

[TIP]
====
The auto-configured javadoc:org.springframework.boot.task.ThreadPoolTaskExecutorBuilder[] or
javadoc:org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder[] allow you to easily create instances of type javadoc:org.springframework.core.task.AsyncTaskExecutor[] that replicate the default behavior of auto-configuration.

include-code::TaskExecutionConfigurationExamples[tag=executor-builder]
====

If a `taskExecutor` named bean is not an option, you can mark your bean as javadoc:org.springframework.context.annotation.Primary[format=annotation] or define an
javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean to specify the
`Executor` responsible for handling regular task execution with javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation].
The following example demonstrates how to achieve this.

include-code::TaskExecutionConfigurationExamples[tag=async-configurer]

To register a custom javadoc:java.util.concurrent.Executor[] while keeping the auto-configured
javadoc:org.springframework.core.task.AsyncTaskExecutor[], you can create a custom
javadoc:java.util.concurrent.Executor[] bean and set the `defaultCandidate=false` attribute in its
javadoc:org.springframework.context.annotation.Bean[format=annotation] annotation, as demonstrated in the following example:

include-code::TaskExecutionConfigurationExamples[tag=default-candidate-task-executor]

In that case, you will be able to javadoc:org.springframework.beans.factory.annotation.Autowired[format=annotation]
your custom javadoc:java.util.concurrent.Executor[] into other components while retaining the auto-configured `AsyncTaskExecutor`.
However, remember to use the javadoc:org.springframework.beans.factory.annotation.Qualifier[format=annotation] annotation alongside javadoc:org.springframework.beans.factory.annotation.Autowired[format=annotation].

If for some reason, it is not possible, you can request Spring Boot to auto-configure an `AsyncTaskExecutor` anyway, as follows:

[configprops,yaml]
----
Expand All @@ -15,31 +84,27 @@ spring:
mode: force
----

The auto-configured executor will be automatically used for:
The auto-configured javadoc:org.springframework.core.task.AsyncTaskExecutor[] will be used automatically for all integrations, even if a custom javadoc:java.util.concurrent.Executor[] bean is registered, including those marked as javadoc:org.springframework.context.annotation.Primary[format=annotation].
These integrations include:

- Asynchronous task execution (`@EnableAsync`), unless an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean is present.
- Asynchronous task execution (javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation]), unless an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean is present.
- Spring for GraphQL's asynchronous handling of javadoc:java.util.concurrent.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.

[TIP]
====
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.
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`.

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[].

Another option is to define those beans explicitly.
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.
====

[NOTE]
[WARNING]
====
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`.
If the auto-configured `AsyncTaskExecutor` is not defined, only regular task execution fallbacks to a bean named `taskExecutor` to match Spring Framework's behavior.
When `force` mode is enabled, `applicationTaskExecutor` will also be configured for regular task execution with javadoc:org.springframework.scheduling.annotation.EnableAsync[format=annotation], even if a javadoc:org.springframework.context.annotation.Primary[format=annotation] bean or a bean named `taskExecutor` of type javadoc:java.util.concurrent.Executor[] is present.
The only way to override the `Executor` for regular tasks is by registering an javadoc:org.springframework.scheduling.annotation.AsyncConfigurer[] bean.
====


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.
Those default settings can be fine-tuned using the `spring.task.execution` namespace, as shown in the following example:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.docs.features.taskexecutionandscheduling;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

public class TaskExecutionConfigurationExamples {

// tag::default-candidate-task-executor[]
@Bean(defaultCandidate = false)
@Qualifier("scheduledExecutorService")
ScheduledExecutorService scheduledExecutorService() {
return Executors.newSingleThreadScheduledExecutor();
}
// end::default-candidate-task-executor[]

// tag::application-task-executor[]
@Bean("applicationTaskExecutor")
SimpleAsyncTaskExecutor applicationTaskExecutor() {
return new SimpleAsyncTaskExecutor("app-");
}
// end::application-task-executor[]

// tag::executor-builder[]
@Bean
SimpleAsyncTaskExecutor taskExecutor(SimpleAsyncTaskExecutorBuilder builder) {
return builder.build();
}
// end::executor-builder[]

static class MultipleTaskExecutor {

// tag::multiple-task-executor[]
@Bean("applicationTaskExecutor")
SimpleAsyncTaskExecutor applicationTaskExecutor() {
return new SimpleAsyncTaskExecutor("app-");
}

@Bean("taskExecutor")
ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("async-");
return threadPoolTaskExecutor;
}
// end::multiple-task-executor[]

}

// tag::async-configurer[]
@Configuration(proxyBeanMethods = false)
public class TaskExecutionConfiguration {

@Bean
AsyncConfigurer asyncConfigurer(ExecutorService executorService) {
return new AsyncConfigurer() {

@Override
public Executor getAsyncExecutor() {
return executorService;
}

};
}

@Bean
ExecutorService executorService() {
return Executors.newCachedThreadPool();
}

}
// end::async-configurer[]

}