There appears to be a bug in TaskExecutorConfiguration when creating an AsyncTaskExecutor bean, the condition is:
@ConditionalOnMissingBean(Executor.class)
instead of on AsyncTaskExecutor.
The result is, if an app creates a bean of type Executor (such as a ScheduledExecutorService), this results in Spring’s default AsyncTaskExecutor beans to not be created. The application will fail to start because of the missing beans.
This is for apps trying to use the bean generated for the configuration with the Task Execution and Scheduling feature. If an app creates a bean of type Executor, the above bean never gets created.
Comment From: blake-bauman
I've attached a sample project which reproduced the issue. If you run it, you'll see it fails to start due to a missing AsyncTaskExecutor bean. If you comment out the ScheduledExecutorService
bean, then it succeeds.
test-spring-task-executors.tar.gz
Comment From: quaff
I think @Bean
method level @ConditionalOnMissingBean
should be used instead of class level @ConditionalOnMissingBean(Executor.class)
.
It should only back off for TaskExecutor
not Executor
.
My proposal: https://github.com/spring-projects/spring-boot/compare/main...quaff:patch-137?expand=1
Comment From: quaff
Or @ConditionalOnMissingBean(value =Executor.class, ignored = ScheduledExecutorService.class)
?
It make sense since ScheduledExecutorService
should be used for scheduling task only.
Comment From: snicoll
It should only back off for TaskExecutor not Executor.
I disagree with that and this bit of the auto-configuration is a bit tricky, I reckon. This is due to the bridge we had before the executor abstraction was available in the JDK. The condition on Executor
is on purpose as users may define such a bean and expects the auto-configuration to back-off.
Comment From: blake-bauman
@quaff ScheduledExecutorService was just an example. Any bean of type Executor will cause an app to fail startup if they need the AsyncTaskExecutor.
users may define such a bean and expects the auto-configuration to back-off
@snicoll We've found the opposite. Users define a bean for other purposes but expect to still use the AsyncTaskExecutor for other tasks.
Comment From: nosan
@blake-bauman
Users define a bean for other purposes but expect to still use the AsyncTaskExecutor for other tasks.
In this case, you can use defaultCandidate=false
annotation attribute and inject your ScheduledExecutorService
using the @Qualifier
annotation.
Here's an example based on what you shared earlier, with a slight adjustment:
@Bean(defaultCandidate = false)
@Qualifier("myexecutor")
public ScheduledExecutorService executorService() {
return Executors.newSingleThreadScheduledExecutor();
}
This way, you will have both the auto-configured Executor
and your custom ScheduledExecutorService
.