Backend Development 12 min read

Using ThreadPoolTaskExecutor for Asynchronous Operations in Spring Boot

This article explains how to configure and use Spring Boot's ThreadPoolTaskExecutor for asynchronous service methods, shows the required @Configuration and @EnableAsync annotations, demonstrates custom thread‑pool monitoring by extending ThreadPoolTaskExecutor, and provides complete code snippets and log examples.

Top Architect
Top Architect
Top Architect
Using ThreadPoolTaskExecutor for Asynchronous Operations in Spring Boot

In a recent project the author needed to speed up batch inserts by off‑loading work to a thread pool. Spring Boot provides a convenient wrapper around Java's ThreadPoolExecutor called ThreadPoolTaskExecutor , which can be enabled with the @EnableAsync annotation.

Step 1 – Create a thread‑pool configuration class

@Configuration
@EnableAsync
public class ExecutorConfig {

    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);

    @Value("${async.executor.thread.core_pool_size}")
    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size}")
    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity}")
    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix}")
    private String namePrefix;

    @Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix(namePrefix);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

The corresponding application.properties entries are:

# Async thread configuration
# Core thread count
async.executor.thread.core_pool_size = 5
# Max thread count
async.executor.thread.max_pool_size = 5
# Queue capacity
async.executor.thread.queue_capacity = 99999
# Thread name prefix
async.executor.thread.name.prefix = async-service-

Step 2 – Define an asynchronous service interface

public interface AsyncService {
    /** Execute asynchronous task */
    void executeAsync();
}

Step 3 – Implement the service and annotate the method

@Service
public class AsyncServiceImpl implements AsyncService {

    private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);

    @Override
    @Async("asyncServiceExecutor")
    public void executeAsync() {
        logger.info("start executeAsync");
        System.out.println("异步线程要做的事情");
        System.out.println("可以在这里执行批量插入等耗时的事情");
        logger.info("end executeAsync");
    }
}

Inject the service in a controller and call it:

@Autowired
private AsyncService asyncService;

@GetMapping("/async")
public void async() {
    asyncService.executeAsync();
}

Running the endpoint produces logs similar to:

2018-07-16 22:15:47.655  INFO 10516 --- [async-service-5] AsyncServiceImpl : start executeAsync
异步线程要做的事情
可以在这里执行批量插入等耗时的事情
2018-07-16 22:15:47.655  INFO 10516 --- [async-service-5] AsyncServiceImpl : end executeAsync
... (repeated for other threads)

These logs show that multiple threads from the pool are handling the work while the original request returns quickly.

Optional – Monitoring the thread pool

To see the pool status on each submission, extend ThreadPoolTaskExecutor and override the execution methods:

public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);

    private void showThreadPoolInfo(String prefix) {
        ThreadPoolExecutor tp = getThreadPoolExecutor();
        if (tp == null) return;
        logger.info("{}, {}, taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                getThreadNamePrefix(), prefix,
                tp.getTaskCount(), tp.getCompletedTaskCount(), tp.getActiveCount(), tp.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }

    @Override
    public Future
submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }

    // other overrides omitted for brevity
}

Replace the executor creation in ExecutorConfig with new VisiableThreadPoolTaskExecutor() to get detailed logs such as:

async-service-, 2. do submit, taskCount [3], completedTaskCount [3], activeCount [0], queueSize [0]

This confirms how many tasks have been submitted, completed, and whether any are waiting in the queue.

Overall, the article demonstrates a complete workflow: configuring a Spring‑Boot thread pool, exposing asynchronous service methods, invoking them from a controller, and optionally adding runtime visibility into the pool’s behavior.

JavaSpring Bootthread poolasyncThreadPoolTaskExecutor
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login 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.