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.
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
@EnableAsyncExample 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.runAsyncexecutes 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
DeferredResultlets 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.
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.
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.
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.
