Backend Development 11 min read

Asynchronous Execution Techniques in Spring Boot: CompletableFuture, @Async, WebAsyncTask, DeferredResult and More

This article explains multiple ways to achieve asynchronous processing in Spring Boot, covering @Async annotation, CompletableFuture, WebAsyncTask, DeferredResult, Tomcat connection tuning, component scanning, Undertow migration, BufferedWriter usage, and AsyncHandlerInterceptor with detailed code examples.

Top Architect
Top Architect
Top Architect
Asynchronous Execution Techniques in Spring Boot: CompletableFuture, @Async, WebAsyncTask, DeferredResult and More

The article presents a collection of asynchronous execution methods for Spring Boot backend services, including @Async annotation, CompletableFuture, WebAsyncTask, DeferredResult, and related configuration optimizations.

Key techniques covered are:

Asynchronous execution with @Async and @EnableAsync

Using CompletableFuture.supplyAsync and CompletableFuture.runAsync

WebAsyncTask with timeout handling

DeferredResult for long‑running tasks and custom timeout logic

Increasing embedded Tomcat's max connections and threads

Switching the default Tomcat container to Undertow for higher throughput

Using @ComponentScan for faster component scanning

BufferedWriter for I/O buffering

AsyncHandlerInterceptor to intercept async calls

CompletableFuture example

@AllArgsConstructor
public class AskThread implements Runnable {
    private CompletableFuture
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
future = new CompletableFuture<>();
        new Thread(new AskThread(future)).start();
        Thread.sleep(1000);
        future.complete(60);
    }
}

The code demonstrates creating a CompletableFuture, starting a thread that waits for the future, and completing the future after a simulated delay.

Using @Async

@EnableAsync
@RestController
public class HelloController {
    @Autowired
    private HelloService hello;

    @GetMapping("/helloworld")
    public String helloWorldController() {
        return hello.sayHello();
    }

    @GetMapping("/async")
    @Async
    public CompletableFuture
asyncHello() {
        return CompletableFuture.supplyAsync(() -> hello.sayHello());
    }
}

Adding @EnableAsync on the configuration class and @Async on a controller method enables asynchronous execution via Spring’s thread pool.

WebAsyncTask with timeout

@RestController
public class HelloController {
    @Autowired
    private HelloService hello;

    @GetMapping("/world")
    public WebAsyncTask
worldController() {
        WebAsyncTask
task = new WebAsyncTask<>(3000, () -> {
            String say = hello.sayHello();
            return say;
        });
        task.onTimeout(() -> {
            throw new TimeoutException("调用超时");
        });
        return task;
    }
}

This snippet shows how to wrap a callable in WebAsyncTask, set a 3‑second timeout, and provide a custom timeout exception.

Tomcat connection tuning

@Configuration
public class TomcatConfig {
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addConnectorCustomizers(new MyTomcatConnectorCustomizer());
        factory.setPort(8005);
        factory.setContextPath("/api-g");
        return factory;
    }

    class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {
        public void customize(Connector connector) {
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
            protocol.setMaxConnections(20000);
            protocol.setMaxThreads(2000);
            protocol.setConnectionTimeout(30000);
        }
    }
}

The configuration increases Tomcat’s maximum connections, threads, and connection timeout.

Switching to Undertow

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

Excluding the default Tomcat starter and adding Undertow improves throughput from ~5000 to ~8000 requests per second.

Using @ComponentScan

@Configuration
@ComponentScan(basePackages = "com.example.service")
public class AppConfig { }

@ComponentScan scans only the specified packages, which is faster than the broader @SpringBootApplication scan.

DeferredResult example

@RestController
public class AsyncDeferredController {
    @Autowired
    private LongTimeTask taskService;

    @GetMapping("/deferred")
    public DeferredResult
executeSlowTask() {
        DeferredResult
result = new DeferredResult<>();
        taskService.execute(result);
        result.onTimeout(() -> result.setErrorResult("time out!"));
        result.onCompletion(() -> System.out.println("onCompletion"));
        return result;
    }
}

DeferredResult allows the controller to return immediately while a background task completes later; timeout and completion callbacks are demonstrated.

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方法");
    }
}

The interceptor logs request processing, handles exceptions, and can modify the response after async handling starts.

By combining these approaches, developers can significantly improve the scalability, responsiveness, and resource utilization of Spring Boot backend applications.

backendJavaCompletableFutureSpring BootasyncDeferredResultWebAsyncTask
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.