Mastering Asynchronous Execution in Spring Boot: From @Async to CompletableFuture

This article explains multiple ways to implement asynchronous processing in Spring Boot, covering @Async annotation, CompletableFuture, supplyAsync, runAsync, WebAsyncTask, DeferredResult, custom interceptors, and server configuration tweaks such as Tomcat connection limits and switching to Undertow.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Mastering Asynchronous Execution in Spring Boot: From @Async to CompletableFuture

1. Asynchronous Execution

Spring Boot provides two primary approaches for asynchronous processing:

Use the @Async annotation on methods and enable it with @EnableAsync on a configuration class.

Leverage JDK 8's CompletableFuture API for more fine‑grained control.

@Async
@EnableAsync

Example using a custom Runnable that waits for a CompletableFuture result:

public class AskThread implements Runnable {
    private CompletableFuture<Integer> re = null;
    public void run() {
        int myRe = 0;
        try {
            myRe = re.get() * re.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(myRe);
    }
    public static void main(String[] args) throws InterruptedException {
        final CompletableFuture<Integer> future = new CompletableFuture<>();
        new Thread(new AskThread(future)).start();
        Thread.sleep(1000); // simulate long computation
        future.complete(60);
    }
}

In this example the thread blocks on re.get() until the future is completed.

2. Using CompletableFuture with supplyAsync

The CompletableFuture.supplyAsync method creates a future that runs the supplied lambda in a separate thread and returns immediately.

public class Calc {
    public static Integer calc(Integer para) {
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        return para * para;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> calc(50))
            .thenApply(i -> Integer.toString(i))
            .thenApply(str -> "\"" + str + "\"")
            .thenAccept(System.out::println);
        future.get();
    }
}

The future runs the slow calc method without blocking the caller.

3. runAsync and Thread Pools

CompletableFuture.runAsync

executes a Runnable without a return value. By default it uses ForkJoinPool.commonPool(), whose threads are daemon threads and will terminate when the main thread ends.

CompletableFuture.runAsync(() -> this.afterBetProcessor(betRequest, betDetailResult, appUser, id));

4. WebAsyncTask for Controller‑Level Async

Spring MVC can return a WebAsyncTask from a controller method, allowing you to set a custom timeout and handle completion or timeout callbacks.

@GetMapping("/world")
public WebAsyncTask<String> worldController() {
    logger.info(Thread.currentThread().getName() + " entering helloController");
    WebAsyncTask<String> task = new WebAsyncTask<>(3000, () -> hello.sayHello());
    task.onCompletion(() -> logger.info(Thread.currentThread().getName() + " completed"));
    task.onTimeout(() -> { throw new TimeoutException("call timeout"); });
    return task;
}

5. DeferredResult for Long‑Running Requests

DeferredResult

lets you produce a result later, after an asynchronous operation finishes. You can also define timeout and completion callbacks.

@GetMapping("/deferred")
public DeferredResult<String> executeSlowTask() {
    logger.info(Thread.currentThread().getName() + " entering executeSlowTask");
    DeferredResult<String> result = new DeferredResult<>();
    taskService.execute(result); // long‑running task sets result later
    result.onTimeout(() -> result.setErrorResult("time out!"));
    result.onCompletion(() -> logger.info(Thread.currentThread().getName() + " onCompletion"));
    return result;
}

6. AsyncHandlerInterceptor for Post‑Processing

An AsyncHandlerInterceptor can modify the response after asynchronous handling completes.

@Component
public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(MyAsyncHandlerInterceptor.class);
    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String resp = "my name is chhliu!";
        response.setContentLength(resp.length());
        response.getOutputStream().write(resp.getBytes());
        logger.info(Thread.currentThread().getName() + " afterConcurrentHandlingStarted");
    }
    // other methods omitted for brevity
}

7. Server Configuration Tweaks

Increasing the embedded Tomcat connector's maximum connections and threads improves throughput.

@Configuration
public class TomcatConfig {
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addConnectorCustomizers(connector -> {
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
            protocol.setMaxConnections(20000);
            protocol.setMaxThreads(2000);
            protocol.setConnectionTimeout(30000);
        });
        factory.setPort(8005);
        factory.setContextPath("/api-g");
        return factory;
    }
}

Switching the embedded server from Tomcat to Undertow can raise throughput from ~5000 to ~8000 requests per second.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

8. Additional Optimizations

Using @ComponentScan directly can be faster than the default @SpringBootApplication scan.

BufferedWriter can be employed for efficient I/O buffering.

Deferred (Spring 5) provides another way to handle async processing.

These techniques together enable high‑performance, non‑blocking request handling in Spring Boot applications.

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.

JavaconcurrencyAsynchronousCompletableFutureSpring Boot
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.