Using @Async in Spring: Default Thread Pools, Custom Thread Pools, and Best Practices
Spring's @Async annotation enables asynchronous method execution, but its default SimpleAsyncTaskExecutor creates a new thread per task, leading to resource issues; the article explains built‑in executors, their drawbacks, and how to implement custom thread pools via AsyncConfigurer, AsyncConfigurerSupport, or a custom TaskExecutor for better control.
Spring provides the @Async annotation (available since Spring 3) to execute methods asynchronously; the caller returns immediately while the actual execution is submitted to a Spring TaskExecutor.
In synchronous calls, the whole process executes sequentially; asynchronous calls send the instruction and continue without waiting for completion, allowing subsequent steps to run in parallel.
Spring includes several built‑in executors:
SimpleAsyncTaskExecutor : creates a new thread for each task, not a true pool.
SyncTaskExecutor : performs tasks synchronously, no threading.
ConcurrentTaskExecutor : an adapter for Executor, not recommended.
SimpleThreadPoolTaskExecutor : Quartz’s SimpleThreadPool, used when both Quartz and non‑Quartz need the pool.
ThreadPoolTaskExecutor : the most common, wraps java.util.concurrent.ThreadPoolExecutor.
The default executor for @Async is SimpleAsyncTaskExecutor, which can cause excessive thread creation and OutOfMemoryError; it offers an optional concurrencyLimit for throttling, but by default it is disabled.
To avoid these problems, a custom thread pool should be defined. Spring allows three main ways to replace the default executor:
Implement the AsyncConfigurer interface and provide a custom Executor via getAsyncExecutor().
Extend AsyncConfigurerSupport and override the executor bean.
Declare a bean of type TaskExecutor (or name it taskExecutor) to be used as the default.
Example of an executor hierarchy:
Executor.class:ThreadPoolExecutorAdapter->ThreadPoolExecutor->AbstractExecutorService->ExecutorService->ExecutorAnd for TaskExecutor:
TaskExecutor.class:ThreadPoolTaskExecutor->SchedulingTaskExecutor->AsyncTaskExecutor->TaskExecutorWhen configuring a custom pool, developers can set core size, max size, queue capacity, rejection policy, and exception handling to suit the application’s concurrency requirements.
Finally, the article notes best practices from Alibaba’s Java development guidelines, discouraging the use of Executors factory methods (e.g., newFixedThreadPool, newCachedThreadPool) due to potential memory and thread‑count issues.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
