Master Asynchronous Execution in Spring Boot: @Async, CompletableFuture, WebAsyncTask & More

This article explores multiple ways to implement asynchronous processing in Spring Boot, covering @Async with @EnableAsync, CompletableFuture, DeferredResult, WebAsyncTask, AsyncHandlerInterceptor, as well as related server tweaks like increasing Tomcat connections, switching to Undertow, and using @ComponentScan for faster scanning.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Master Asynchronous Execution in Spring Boot: @Async, CompletableFuture, WebAsyncTask & More

Asynchronous Execution

Two implementation methods are presented: using the asynchronous annotation @Async together with @EnableAsync, and leveraging JDK 8's CompletableFuture.

@AllArgsConstructor
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();
        // Simulate a long‑running computation
        Thread.sleep(1000);
        // Complete the future with the result
        future.complete(60);
    }
}

In this example a thread is started before the AskThread receives its data; the call to myRe = re.get() * re.get() blocks until the future is completed.

Another example demonstrates using CompletableFuture.supplyAsync to run a slow calculation without blocking the caller.

public class Calc {
    public static Integer calc(Integer para) {
        try {
            // Simulate a long execution
            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();
    }
}
CompletableFuture.supplyAsync

creates a new thread to execute the supplied function, returning immediately; the resulting CompletableFuture acts as a contract for obtaining the final result later. For fire‑and‑forget tasks, CompletableFuture.runAsync(Runnable) is often used. Both methods run in the ForkJoinPool.common daemon thread pool, which terminates when the main thread ends.

Increasing Embedded Tomcat Max Connections

@Configuration
public class TomcatConfig {
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory tomcatFactory = new TomcatServletWebServerFactory();
        tomcatFactory.addConnectorCustomizers(new MyTomcatConnectorCustomizer());
        tomcatFactory.setPort(8005);
        tomcatFactory.setContextPath("/api-g");
        return tomcatFactory;
    }
    class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {
        public void customize(Connector connector) {
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
            // Set max connections
            protocol.setMaxConnections(20000);
            // Set max threads
            protocol.setMaxThreads(2000);
            protocol.setConnectionTimeout(30000);
        }
    }
}

Using @ComponentScan() for Faster Scanning

Applying @ComponentScan() directly can locate packages faster than using @SpringBootApplication, which implicitly includes component scanning.

Switching Default Tomcat to Undertow

Replacing the default embedded Tomcat with Undertow improves throughput (approximately 8000 requests per second vs. 5000 for Tomcat).

<exclusions>
  <exclusion>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
  </exclusion>
</exclusions>

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

Using BufferedWriter for Buffering

BufferedWriter can be used to buffer output streams; readers are encouraged to try it on their own.

Deferred Approach for Asynchronous Calls

@RestController
public class AsyncDeferredController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final LongTimeTask taskService;
    @Autowired
    public AsyncDeferredController(LongTimeTask taskService) {
        this.taskService = taskService;
    }
    @GetMapping("/deferred")
    public DeferredResult<String> executeSlowTask() {
        logger.info(Thread.currentThread().getName() + "进入executeSlowTask方法");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        // Invoke a long‑running task
        taskService.execute(deferredResult);
        logger.info(Thread.currentThread().getName() + "从executeSlowTask方法返回");
        deferredResult.onTimeout(() -> {
            logger.info(Thread.currentThread().getName() + " onTimeout");
            deferredResult.setErrorResult("time out!");
        });
        deferredResult.onCompletion(() -> logger.info(Thread.currentThread().getName() + " onCompletion"));
        return deferredResult;
    }
}

Async Calls with AsyncHandlerInterceptor

@Component
public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(MyAsyncHandlerInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info(Thread.currentThread().getName() + "服务调用完成,返回结果给客户端");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (ex != null) {
            System.out.println("发生异常:" + ex.getMessage());
        }
    }
    @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方法");
    }
}

WebAsyncTask for Asynchronous Requests with Timeout

@RestController
public class HelloController {
    private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
    @Autowired
    private HelloService hello;
    @GetMapping("/helloworld")
    public String helloWorldController() {
        return hello.sayHello();
    }
    @GetMapping("/hello")
    public Callable<String> helloController() {
        logger.info(Thread.currentThread().getName() + " 进入helloController方法");
        Callable<String> callable = () -> {
            logger.info(Thread.currentThread().getName() + " 进入call方法");
            String say = hello.sayHello();
            logger.info(Thread.currentThread().getName() + " 从helloService方法返回");
            return say;
        };
        logger.info(Thread.currentThread().getName() + " 从helloController方法返回");
        return callable;
    }
    @GetMapping("/world")
    public WebAsyncTask<String> worldController() {
        logger.info(Thread.currentThread().getName() + " 进入helloController方法");
        WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, () -> hello.sayHello());
        webAsyncTask.onCompletion(() -> logger.info(Thread.currentThread().getName() + " 执行完毕"));
        webAsyncTask.onTimeout(() -> {
            logger.info(Thread.currentThread().getName() + " onTimeout");
            throw new TimeoutException("调用超时");
        });
        return webAsyncTask;
    }
    @GetMapping("/exception")
    public WebAsyncTask<String> exceptionController() {
        logger.info(Thread.currentThread().getName() + " 进入helloController方法");
        Callable<String> callable = () -> {
            logger.info(Thread.currentThread().getName() + " 进入call方法");
            throw new TimeoutException("调用超时!");
        };
        logger.info(Thread.currentThread().getName() + " 从helloController方法返回");
        return new WebAsyncTask<>(20000, callable);
    }
}

The image below illustrates the article’s focus on asynchronous techniques.

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.

JavaCompletableFutureSpring BootDeferredResultWebAsyncTaskAsyncHandlerInterceptor
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

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.