Both TaskExecutionAutoConfiguration and TaskSchedulingAutoConfiguration create Executor implementations. The first creates ThreadPoolTaskExecutor while the second creates ThreadPoolTaskScheduler. Both extend ExecutorConfigurationSupport and implement AsyncListenableTaskExecutor and SchedulingTaskExecutor. The later however also implements TaskScheduler which is needed when you add @EnableScheduling to your application.
Tickets #15983 and #15984 made the TaskSchedulingAutoConfiguration run after TaskExecutionAutoconfiguration which was, in my opinion bad decisions. Since @EnableScheduling requires TaskScheduler then (unless you have other implementation) ThreadPoolTaskScheduler will be created which results in duplicate Executor bean (actually duplicate of all interfaces provided by ThreadPoolTaskExecutor). If your application is using @Autowire Executor along with @EnableScheduling you will get following error:
Field executor in XXX required a single bean, but 2 were found:
- applicationTaskExecutor: defined by method 'applicationTaskExecutor' in class path resource [org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class]
- taskScheduler: defined by method 'taskScheduler' in class path resource [org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class]
I order to avoid the error you must use @Qualifier for all injected @Executor instances - which is definitely against Boot philosophy of "smart default".
If the TaskSchedulingAutoConfiguration was executed before TaskExecutionAutoConfiguration then the Executor would be already created and TaskExecutorAutoConfiguration would be skipped.
Since the ThreadPoolTaskExecutor is completely inferior to ThreadPoolTaskScheduler you should consider if it can be deprecated and ThreadPoolTaskScheduler could be created when application needs Executor.
If you want to retain ThreadPoolTaskExecutor and keep the order then you should at least annotate the ThreadPoolTaskExecutor with @Primary. This was suggested in #15747, but it was closed without resolving the issue.
The issue was actually reported in #15729, but it was closed in favor of #15748. (And many others).
The issues were generally solved by forcing the code to prefer "applicationTaskExecutor", but that does not help regular users that require Executor for their own purpose. The fact is that adding TaskExecutionAutoConfiguration in 2.1 introduced end user issues that were not resolved properly.
What about making ThreadPoolTaskScheduler to implement only TaskScheduler interface by delegating to ThreadPoolTaskExecutor? After all @EnableScheduling only needs TaskScheduler.
This is minimal application that will fail:
@SpringBootApplication
@EnableScheduling
public class Main {
public static void main(String[] args) throws Exception {
SpringApplication.run(Main.class, args).close();
}
@Autowired Executor executor;
}
Comment From: rainerfrey
If I as an application developer may voice my opinion here:
IMO scheduling and task execution are two separate needs, and the required characteristics the underlying executor service might be quite different. Therefore I'm very much in favour of keeping a distinct task executor and task scheduler instance, that is configured separately. Therefore I also see no point in using the same implementation class, although I don't really care about that point.
With a concept as generic as an executor, an application simply should deal with the fact that the framework could provide more than one instance for different purposes.
Also, in my opinion, the applicationTaskExecutor is provided to use with Spring's task execution abstractions like @Async or the TaskExecutor interface. If an application needs an executor service that doesn't fit these abstractions, chances are that the configuration of the provided applicationTaskExecutor isn't even suitable for the usage. An application should then make the conscious decision to either inject the applicationTaskExecutor explicitly on purpose, or provide its own Executor bean and inject that (with a qualifier).
Taking this thought even further, the change that I would like to see, is:
don't opt out of autoconfiguring the default applicationTaskExecutor on the mere presence of any Executor bean, but go by the more specific TaskExecutor interface, or even the bean name applicationTaskExecutor and taskExecutor, so that default @Async execution works as with any other Spring Boot application, even if there is an Executor bean that might be defined for a completely different purpose.
Use case for that last point that I implemented yesterday: very specific @Async methods (using a qualifier in the annotation) should be executed in a single thread executor, but other async methods probably not. Defining the single thread executor bean kept the auto configured task executor from being created. I redefined it explicitly with an autowired TaskExecutorBuilder.
Comment From: AlesD
I do agree that scheduling and task execution are two separate things. In fact task scheduling only needs implementation of TaskScheduler. If you want to separate task execution from task scheduling then use of @EnableScheduling should provide only TaskScheduler and not Executor. Maybe the ThreadPoolTaskScheduler should not inherit from ExecutorConfigurationSupport, but only have it as private property.
I do not agree, however, that application should provide it's own Executor for purposes other than @Async, because when it does so, then this executor will prevent TaskExecutionAutoConfiguration from creating applicationTaskExecutor. Since many of bugs created by introduction TaskExecutionAutoConfiguration have been "fixed" by using @Qualifier("applicationTaskExecutor") introduction of own Executor might actually break things, unless the condition on TaskExecutionAutoConfiguration#applicationTaskExecutor is modified.
Try this code:
@SpringBootApplication
public class Main {
public static void main(String[] args) throws Exception {
SpringApplication.run(Main.class, args).close();
}
// Comment out this method and it will work
@Bean
public ExecutorService executorService() {
return Executors.newSingleThreadExecutor();
}
// Remove the qualifier and it will work - until you add @EnableScheduling
@Bean
public Object executorUser(@Qualifier("applicationTaskExecutor") Executor executor) {
return new Object();
}
}
As you can see there is always some issue. If you use @EnableScheduling you must add @Qualifier("applicationTaskExecutor") to autowired executors. And if you create your own executor then applicationTaskExecutor won't be created. Note that the code is actually creating ExecutorService which is not provided by TaskExecutionAutoConfiguration, but it still disables auto configuration since ExecutorService or ScheduledExecutorService extends Executor. I should probably create separate ticket for this since there is already code that depends on existence of executor named applicationTaskExecutor that won't work if you provide your own executor.
Truth is that introduction of TaskExecutorAutoConfiguration in 2.1 brought many issues. And users still can not simply write @Autowired Executor if they need Executor instance.
Best solution to this issue is probably that @EnableScheduling should provide only implementation of TaskScheduler and not Executor.
Comment From: Agh42
I ran into this problem just now (on Spring Boot v2.4.2).
I am not autowiring an Executor. I just put @EnableScheduling in my application configuration and annotated a method with @Scheduled:
@Configuration
@EnableScheduling
@EnableAsync
public class ApplicationConfiguration {
}
@Component
public class NotificationProducer {
...
@Scheduled(cron = "${cveservice.notifications.cron}")
public void produceNotifications() {
...
This produces the error message (No qualifying bean of type 'org.springframework.core.task.TaskExecutor' available: expected single matching bean but found 2: applicationTaskExecutor,taskScheduler").
So what would be the recommended solution to make @EnableAsync work together with @EnableScheduling right now?
Comment From: snicoll
@Agh42 unfortunately, that's the problem when pasting part of your project in text like this. Copying those bits in a project from start.spring.io works as expected.
Back to the original issue, I don't think I am following the concrete problem of injecting an Executor is. The auto-configurations that were added to support async and scheduling are auto-configuring additional pieces and I agree the current hierarchy of TaskExecutor and TaskScheduler can be a bit confusing but there is nothing we can do in Spring Boot about that.
If the TaskSchedulingAutoConfiguration was executed before TaskExecutionAutoConfiguration then the Executor would be already created and TaskExecutorAutoConfiguration would be skipped.
Unfortunately, I don't agree with that. Making them separate was done on purpose as scheduling and asynchronous execution are two completely separate things. Most users don't want the same pooling for those for instance. The combinations of making them separate and the fact that Spring Framework is structured that way means that indeed we need to qualify them one way or the other.
What about making ThreadPoolTaskScheduler to implement only TaskScheduler interface by delegating to ThreadPoolTaskExecutor? After all @EnableScheduling only needs TaskScheduler.
That's something you'd have to report against Spring Framework. Based on the outcome, we could follow-up here accordingly.
This issue is quite involved so I'd like to see if I've missed anything else.
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: spring-projects-issues
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.
Comment From: emeraldhieu
Has this issue been fixed? I got the same error when using both @EnableAsync and @EnableScheduling. Is there any workaround?
Comment From: wilkinsona
@emeraldhieu Unfortunately, this issue never really went anywhere.
I suspect that each person involved in the discussion had a slightly different problem and we could not reproduce some of the problems that were mentioned. As far as I know, nothing was reported to Spring Framework so ThreadPoolTaskScheduler continues to be both a TaskScheduler and a TaskExecutor.
If the failure that you are experiencing is occurring in your own code, you should use a qualifier to make it clear which you want to be injected. If the failure is occurring in Spring Boot's code, as @Agh42 reported and we could not reproduce, please provide a minimal sample that reproduces the problem and we will investigate.
Comment From: mirceade
I don't get it... this does indeed occur in Spring Boot's code and it is sufficient to use @EnableAsync and @EnableScheduling and call an @Async method. How is this not a defect? How can one specify a qualifier in this case?
Comment From: wilkinsona
@mirceade please read the comment immediately above yours. Nothing's changed since then and the offer to investigate the problem when a minimal samples is provided still stands.