TaskExecutionAutoConfiguration.applicationTaskExecutor is conditional on missing bean java.util.concurrent.Executor. This results in a java.util.concurrent.ThreadPoolExecutor bean stopping instantiation of the applicationTaskExecutor.

https://github.com/spring-projects/spring-boot/blob/1bca2bbc859b0a40b6b5616c0b6433e6047c36ca/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java#L75-L81

However, a ThreadPoolExecutor is not a Spring TaskExecutor. So eventually (somewhere) a stop-gap SimpleAsyncTaskExecutor is created, and then warnings start getting logged:

!!!
An Executor is required to handle java.util.concurrent.Callable return values.
Please, configure a TaskExecutor in the MVC config under "async support".
The SimpleAsyncTaskExecutor currently in use is not suitable under load.
-------------------------------
Request URI: '/api/whatever'
!!!

I believe there is an implicit assumption here that any Executor bean defined would naturally be a Spring TaskExecutor, because it's a Spring program. This seems to me to be a bad assumption.

I propose one of the following:

  1. Make the applicationTaskExecutor bean conditional on TaskExecutor instead of just Executor. That way, other Executor or ExecutorService beans which are not also TaskExecutor will not prevent the applicationTaskExecutor from being created.
  2. If an Executor is defined, create a TaskExecutor wrapping the given executor. If an ExecutorService is defined, an AsyncTaskExecutor wrapper could be created instead. Based on the interfaces, these wrappers would be very simple pass-through implementations, as the Spring interfaces are basically subsets of the Java ones.

Happy to help with pull requests, but I thought this warranted some discussion first.

Comment From: jdmichal

Ends up the wrapper necessary for (2) already exists: org.springframework.core.task.support.TaskExecutorAdapter.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/task/support/TaskExecutorAdapter.html

That said, I want to clarify that I would prefer solution (1), because users might legitimately want to define additional Executor and ExecutorService beans without replacing the default TaskExecutor.

Comment From: snicoll

@jdmichal thanks for the report but the condition is set on Executor on purpose. The application task executor is used for async processing (@EnableAsync) and asynchronous MVC handling. The former requires an Executor while the latter requires an AsyncTaskExecutor.

If you're not using any of the MVC async feature, using an Executor works just fine and asynchronous processing will use the executor that you've defined. If you do however, it is important to either provide a tailored AsyncTaskExecutor or let Spring Boot configure one for you.

Doing what you've described would force users of async processing to provide an implementation that isn't actually necessary. I understand this is not ideal but the warning is useful to let you know that your custom executor is not considered and that something needs to be fixed. Creating two Executors behind the scenes is something we're not keen to do, hence the current situation.

Finally, there is an explicit note in the documentation about this.