Mastering SpringBoot @Async: Thread Pools, Custom Executors, and Virtual Threads

This guide explains how SpringBoot's @Async annotation works, why to use it, how to configure default and custom thread pools, handle exceptions and return values, and leverage virtual threads for higher throughput in Java backend applications.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering SpringBoot @Async: Thread Pools, Custom Executors, and Virtual Threads

1. Introduction

In a SpringBoot 3.2.5 environment, the @Async annotation marks methods for asynchronous execution, allowing them to run in separate threads without blocking the caller, which improves system concurrency and response speed.

Typical reasons to use @Async are:

Improve system performance : time‑consuming operations such as heavy database queries, complex calculations, or remote service calls run in background threads, freeing the main thread.

Enhance user experience : web pages do not have to wait for long‑running tasks before responding to user actions.

Simplify code : the annotation abstracts away low‑level thread handling, letting developers focus on business logic.

2. Application Details

2.1 Default Thread Pool

By default SpringBoot allows up to 8 concurrent async tasks; additional tasks wait in a queue whose capacity is Integer.MAX_VALUE.

@Resource
private TaskService taskServie;
public void task() throws Throwable {
  this.taskServie.task1();
  this.taskServie.task2();
  this.taskServie.task3();
  this.taskServie.task4();
  this.taskServie.task5();
  this.taskServie.task6();
  this.taskServie.task7();
  this.taskServie.task8();
  this.taskServie.task9();
}

@Async
public void task1() throws Exception {
  System.err.printf("当前执行的线程: %s%n", Thread.currentThread().getName());
  TimeUnit.SECONDS.sleep(100);
}
// other 8 async tasks are similar to task1

Running the above prints thread names for tasks 1‑8; the ninth task enters the waiting queue.

当前执行的线程: task-1
当前执行的线程: task-2
当前执行的线程: task-3
当前执行的线程: task-4
当前执行的线程: task-5
当前执行的线程: task-6
当前执行的线程: task-7
当前执行的线程: task-8

The ninth task is queued.

Configuring Thread Pool Size

spring:
  task:
    execution:
      pool:
        core-size: 10
        max-size: 20
        queue-capacity: 100

2.2 Custom Thread Pool via AsyncConfigurer

@Component
public class PackAsyncConfigurer implements AsyncConfigurer {
  @Override
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(4);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(100);
    executor.afterPropertiesSet();
    return executor;
  }
}

When a custom executor is defined programmatically, the properties in application.yml are ignored.

2.3 Specifying Executor for a Particular Async Task

@Bean
ThreadPoolTaskExecutor taskExecutor1() {
  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  // configure executor
  return executor;
}

@Async("taskExecutor1")
public void task4() throws Exception {
  // task implementation
}

You can create multiple executors and assign them to different async methods, but be mindful of each pool’s size.

2.4 Async Exception Handling

By default, uncaught exceptions from async methods are logged by SimpleAsyncUncaughtExceptionHandler . You can provide a custom handler via AsyncConfigurer:

@Component
public class PackAsyncConfigurer implements AsyncConfigurer {
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new AsyncUncaughtExceptionHandler() {
      @Override
      public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        System.err.printf("异步任务: %s发生异常, 错误消息: %s%n",
          method.getDeclaringClass() + "#" + method.getName(), ex.getMessage());
      }
    };
  }
}

2.5 Async Return Values

Async methods usually return void, but you can return CompletableFuture<String> (or ListenableFuture, Future) to obtain results.

@Async
public CompletableFuture<String> runTask() throws Exception {
  System.err.printf("当前执行的线程: %s%n", Thread.currentThread().getName());
  TimeUnit.SECONDS.sleep(1);
  return CompletableFuture.completedFuture("数据获取成功");
}

public void task() throws Throwable {
  System.out.printf("%s start...%n", Thread.currentThread().getName());
  CompletableFuture<String> task = this.taskServie.runTask();
  String ret = task.join();
  System.out.printf("获取结果: %s%n", ret);
  System.out.printf("%s end...%n", Thread.currentThread().getName());
}

Alternatively, handle the result asynchronously:

this.taskServie.runTask().whenComplete((ret, ex) -> {
  if (ex != null) {
    System.err.printf("发生错误: %s%n", ex.getMessage());
    return;
  }
  System.out.printf("获取结果: %s%n", ret);
});

2.6 Virtual Threads (JDK 21)

SpringBoot 3.2.x supports executing async tasks on virtual threads. Enable it with:

spring:
  threads:
    virtual:
      enabled: true

The regular thread‑pool configuration remains unchanged, but when virtual threads are used the core/max pool settings become ineffective.

@Async
public void task1() throws Exception {
  System.err.printf("当前执行的线程: %s, 是否虚拟线程: %b%n",
    Thread.currentThread().getName(), Thread.currentThread().isVirtual());
}

Running the method prints something like:

当前执行的线程: vm-pack-task-1, 是否虚拟线程: true

Thus the task runs on a virtual thread.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

concurrencyThreadPoolSpringBootAsyncVirtualThread
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.