Master Asynchronous Programming in Spring Boot: From Basics to Custom Thread Pools
This article explains why asynchronous execution is needed in Spring Boot, demonstrates how to enable @Async, shows step‑by‑step code for configuration, method annotation, and controller usage, and details how to create and manage custom thread pools for optimal performance and fault tolerance.
Why Use Asynchronous Programming in Spring Boot?
In typical Spring Boot development calls are synchronous, but scenarios like user registration with bonus points or order confirmation with push notifications benefit from async execution to improve performance and fault tolerance.
Fault tolerance: secondary tasks (e.g., awarding points) should not cause the primary operation (registration) to fail.
Performance: parallel execution reduces total response time.
Enabling Async Support in Spring Boot
Spring provides the @Async annotation for async methods and the @EnableAsync annotation to activate the feature.
Step 1 – Create a Configuration Class
@Configuration
@EnableAsync
public class AsyncConfiguration {
}Step 2 – Annotate Business Methods
@Component
@Slf4j
public class AsyncTask {
@SneakyThrows
@Async
public void doTask1() {
long t1 = System.currentTimeMillis();
Thread.sleep(2000);
long t2 = System.currentTimeMillis();
log.info("task1 cost {} ms", t2 - t1);
}
@SneakyThrows
@Async
public void doTask2() {
long t1 = System.currentTimeMillis();
Thread.sleep(3000);
long t2 = System.currentTimeMillis();
log.info("task2 cost {} ms", t2 - t1);
}
}Step 3 – Call Async Methods from a Controller
@RestController
@RequestMapping("/async")
@Slf4j
public class AsyncController {
@Autowired
private AsyncTask asyncTask;
@RequestMapping("/task")
public void task() throws InterruptedException {
long t1 = System.currentTimeMillis();
asyncTask.doTask1();
asyncTask.doTask2();
Thread.sleep(1000);
long t2 = System.currentTimeMillis();
log.info("main cost {} ms", t2 - t1);
}
}Visiting http://localhost:8080/async/task shows that the main thread finishes in about 1 s while the async tasks run independently, confirming reduced response time.
Why Provide a Custom Thread Pool for @Async?
The default SimpleAsyncTaskExecutor creates a new thread for each task and does not reuse threads, which can quickly exhaust memory and cause OutOfMemoryError.
Spring’s Built‑in Executors
Spring offers several executors: SimpleAsyncTaskExecutor : creates a new thread per task, no reuse. SyncTaskExecutor : executes tasks synchronously. ConcurrentTaskExecutor : adapter, rarely needed. ThreadPoolTaskScheduler : supports cron expressions. ThreadPoolTaskExecutor : most common, wraps java.util.concurrent.ThreadPoolExecutor .
Defining a Custom Thread Pool
@Configuration
@EnableAsync
public class SyncConfiguration {
@Bean(name = "asyncPoolTaskExecutor")
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(10);
taskExecutor.setMaxPoolSize(100);
taskExecutor.setQueueCapacity(50);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setThreadNamePrefix("async-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}After registering this bean, @Async will use the custom pool.
Handling Multiple Thread Pools
High‑concurrency endpoints can be isolated by assigning each a dedicated pool. Specify the pool name in the annotation, e.g. @Async("asyncPoolTaskExecutor") or @Async("otherTaskExecutor"). A default pool can be supplied by implementing AsyncConfigurer and overriding getAsyncExecutor().
Example AsyncConfigurer
@Configuration
@EnableAsync
@Slf4j
public class AsyncConfiguration implements AsyncConfigurer {
@Bean(name = "asyncPoolTaskExecutor")
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(2);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(50);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setThreadNamePrefix("async-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
@Override
public Executor getAsyncExecutor() {
return executor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) ->
log.error("Async task error in method: {}", method.getName(), ex);
}
}Now doTask1() runs on the default asyncPoolTaskExecutor, while doTask2() can be directed to otherTaskExecutor for flexible resource allocation.
Conclusion
Async methods annotated with @Async are a powerful tool in Spring Boot development. Understanding how to enable them, provide a proper thread pool, and manage multiple pools is essential for building performant and resilient services.
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.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.
