Master Spring @Async: Custom Thread Pools and Real-World Usage
This guide explains how Spring's @Async annotation works, compares built-in executors, shows how to configure custom thread pools via AsyncConfigurer or AsyncConfigurerSupport, and demonstrates various async method signatures including void, Future and CompletableFuture with practical code examples.
Overview of @Async
Since Spring 3, the @Async annotation can be placed on a method to execute it asynchronously. The caller returns immediately while the actual execution is submitted to a Spring TaskExecutor, typically a thread pool.
When to Use Custom Thread Pools
In production projects it is recommended to replace the default executor with a custom thread pool by implementing AsyncConfigurer (or extending AsyncConfigurerSupport) and providing a TaskExecutor bean.
Built-in Executors Provided by Spring
SimpleAsyncTaskExecutor – creates a new thread for each task, not a true pool.
SyncTaskExecutor – executes tasks synchronously, no async behavior.
ConcurrentTaskExecutor – an adapter for java.util.concurrent.Executor, rarely needed.
SimpleThreadPoolTaskExecutor – used by Quartz and non-Quartz code.
ThreadPoolTaskExecutor – the most common choice, a wrapper around java.util.concurrent.ThreadPoolExecutor.
Typical Async Method Signatures
void return type for fire-and-forget calls.
Methods with parameters that are passed to the async task.
Methods returning java.util.concurrent.Future (or Spring’s ListenableFuture).
Enabling @Async and Default Executor
When @Async is used without specifying an executor name, Spring falls back to SimpleAsyncTaskExecutor. This executor creates a new thread per task and does not reuse threads, which can lead to high memory consumption.
Void-returning Calls
Annotate the method with @Async. If you need to propagate exceptions, you must catch and re-throw them manually.
Future-returning Calls
CompletableFuture Calls
CompletableFuture does not require @Async; it can be used with any executor to run asynchronous tasks and compose stages.
CompletionStage represents a stage in an asynchronous computation; when one stage finishes it can trigger another.
stage.thenApply(x -> square(x))
.thenAccept(x -> System.out.print(x))
.thenRun(() -> System.out.println());Drawbacks of the Default SimpleAsyncTaskExecutor
Alibaba Java development guidelines advise against using Executors factory methods because they may create unbounded thread pools or queues, leading to OutOfMemoryError. SimpleAsyncTaskExecutor creates a new thread per task unless the concurrencyLimit property is set, which is disabled by default.
Configuring a Custom Thread Pool
Three common approaches:
Implement AsyncConfigurer and override public Executor getAsyncExecutor() to return a ThreadPoolTaskExecutor.
Extend AsyncConfigurerSupport and provide the executor bean.
Define a bean named taskExecutor (or the name referenced by @Async) of type ThreadPoolTaskExecutor.
Example Implementation
Executor.class: ThreadPoolExecutorAdapter -> ThreadPoolExecutor -> AbstractExecutorService -> ExecutorService -> Executor TaskExecutor.class: ThreadPoolTaskExecutor -> SchedulingTaskExecutor -> AsyncTaskExecutor -> TaskExecutorSigned-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.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
