Implementing Asynchronous Tasks in Spring Boot with ThreadPoolTaskExecutor

This article explains how to configure Spring Boot's built‑in ThreadPoolTaskExecutor, customize its core parameters, and use it together with Java 8 CompletableFuture to run asynchronous tasks, including a performance comparison with a synchronous implementation.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Implementing Asynchronous Tasks in Spring Boot with ThreadPoolTaskExecutor

Scenario

When building a Spring Boot application you often need a thread pool for asynchronous processing. The article first reviews the general use of Java's ExecutorService (both Runnable and Callable) and points to external blogs for deeper background. Spring provides its own wrapper, ThreadPoolTaskExecutor, which is a Spring‑managed version of JDK's ThreadPoolExecutor. By default Spring Boot auto‑configures a ThreadPoolTaskExecutor bean, but developers can override the default parameters.

Implementation

1. Configuration in a real project (Ruoyi)

The article uses the open‑source Ruoyi project as an example. The configuration class is placed under com.ruoyi.framework.config and defines a bean named threadPoolTaskExecutor:

package com.ruoyi.framework.config;

import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class ThreadPoolConfig {
    // core pool size
    private int corePoolSize = 50;
    // maximum pool size
    private int maxPoolSize = 200;
    // queue capacity
    private int queueCapacity = 1000;
    // keep‑alive seconds
    private int keepAliveSeconds = 300;

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // rejection policy when the pool is saturated
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

An image (omitted here) shows the location of the configuration file within the Ruoyi codebase.

2. Using CompletableFuture for asynchronous tasks

Java 8’s CompletableFuture can orchestrate async work. The article links to another blog that demonstrates this pattern.

3. Injecting the executor

Anywhere a thread pool is needed, the configured bean can be autowired:

@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;

4. Performance test with a unit test

A JUnit test measures the time taken when five asynchronous tasks run in parallel using the executor:

@Test
public void test2() {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for (int i = 0; i < 5; i++) {
        int finalI = i;
        CompletableFuture.runAsync(() -> {
            System.out.println(finalI + " executing async operation…");
            int result = 0;
            for (int j = 0; j < 1_000_000; j++) {
                result += j;
            }
            System.out.println("Result:" + result);
        }, threadPoolTaskExecutor);
    }
    stopWatch.stop();
    System.out.println("Total time:" + stopWatch.getLastTaskTimeMillis());
}

The resulting console output (shown in an image) displays the total execution time.

5. Comparison with a synchronous loop

A second test runs the same computation sequentially without async:

@Test
public void test1() {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for (int i = 0; i < 5; i++) {
        int result = 0;
        for (int j = 0; j < 1_000_000; j++) {
            result += j;
        }
        System.out.println("Result:" + result);
    }
    stopWatch.stop();
    System.out.println("Total time:" + stopWatch.getLastTaskTimeMillis());
}

An accompanying image shows the longer total time for the synchronous version, illustrating the performance benefit of using ThreadPoolTaskExecutor with CompletableFuture.

Through these steps the article demonstrates how to configure, inject, and employ Spring’s thread pool for real‑world asynchronous workloads, and provides concrete timing evidence of the speedup achieved.

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.

JavaAsynchronousCompletableFuturespring-bootthread-poolThreadPoolTaskExecutor
The Dominant Programmer
Written by

The Dominant Programmer

Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi

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.