Skip to content

Multiple Executor instances when using @EnableScheduling #20308

@AlesD

Description

@AlesD

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;

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions