Mastering Asynchronous Requests in Spring Boot: Callable, WebAsyncTask & DeferredResult

This article explains how Spring Boot leverages Servlet 3.0 asynchronous processing with four approaches—AsyncContext, Callable, WebAsyncTask, and DeferredResult—detailing their implementation, thread‑pool configuration, and when async endpoints truly boost throughput.

Top Architect
Top Architect
Top Architect
Mastering Asynchronous Requests in Spring Boot: Callable, WebAsyncTask & DeferredResult

Preface

Before Servlet 3.0 each HTTP request was handled by a single thread from start to finish.

Since Servlet 3.0 asynchronous processing can release the container‑allocated thread and related resources, reducing system load and increasing service throughput.

Spring Boot offers four ways to implement asynchronous endpoints (excluding ResponseBodyEmitter, SseEmitter, and StreamingResponseBody): AsyncContext, Callable, WebAsyncTask, and DeferredResult.

Special note: asynchronous handling on the server side is invisible to the client; the response type does not change, and the latency increase is usually negligible.

Implementation Based on Callable

The controller returns a java.util.concurrent.Callable which marks the endpoint as asynchronous.

Controller returns a Callable.

Spring MVC invokes request.startAsync() and submits the Callable to an AsyncTaskExecutor for execution in a separate thread.

The DispatcherServlet and all filters exit the servlet thread while keeping the response open.

When the Callable produces a result, Spring MVC dispatches the request back to the servlet container to complete processing.

The DispatcherServlet processes the asynchronous result.

By default, Callable uses SimpleAsyncTaskExecutor, which does not reuse threads; in production you should configure a real AsyncTaskExecutor.

Implementation Based on WebAsyncTask

WebAsyncTask

is a wrapper around Callable that adds powerful callbacks such as timeout, error, and completion handling.

@GetMapping("/webAsyncTask")
public WebAsyncTask<String> webAsyncTask() {
    WebAsyncTask<String> result = new WebAsyncTask<>(30003, () -> "success");
    result.onTimeout(() -> "timeout callback");
    result.onCompletion(() -> log.info("finish callback"));
    return result;
}
WebAsyncTask

can configure its own timeout, which overrides the global timeout configuration.

Implementation Based on DeferredResult

DeferredResult

works similarly to Callable but the actual result is set later from another thread.

// Global map to store DeferredResult objects
private Map<String, DeferredResult<String>> deferredResultMap = new ConcurrentHashMap<>();

@GetMapping("/testDeferredResult")
public DeferredResult<String> testDeferredResult() {
    DeferredResult<String> deferredResult = new DeferredResult<>();
    deferredResultMap.put("test", deferredResult);
    return deferredResult;
}

@GetMapping("/testSetDeferredResult")
public String testSetDeferredResult() throws InterruptedException {
    DeferredResult<String> deferredResult = deferredResultMap.get("test");
    boolean flag = deferredResult.setResult("testSetDeferredResult");
    if (!flag) {
        log.info("Result already processed, operation ignored");
    }
    return "ok";
}

When the client calls the first endpoint, the request stays in a pending state until another thread sets the result on the stored DeferredResult. It is essential to clean up expired or processed results (e.g., using DeferredResult.isSetOrExpired()) to avoid memory leaks.

deferredResult.onTimeout(() -> "timeout callback");
DeferredResult

also supports a custom timeout that takes precedence over the global timeout.

Providing a Thread Pool

Asynchronous requests should use a dedicated thread pool instead of the default executor.

@Bean("mvcAsyncTaskExecutor")
public AsyncTaskExecutor asyncTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(10);
    executor.setThreadNamePrefix("fyk-mvcAsyncTask-Thread-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setAwaitTerminationSeconds(30);
    executor.initialize();
    return executor;
}

Register the custom executor in MVC async support configuration:

@Configuration
public class FykWebMvcConfigurer implements WebMvcConfigurer {
    @Autowired
    @Qualifier("mvcAsyncTaskExecutor")
    private AsyncTaskExecutor asyncTaskExecutor;

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(60001);
        configurer.setTaskExecutor(asyncTaskExecutor);
    }
}

When to Use Asynchronous Requests

Async requests improve throughput when the request spends most of its time waiting for external resources (e.g., calling other services). They are not beneficial for CPU‑bound work where the thread would be busy the whole time; in such cases simply increasing the worker thread pool is sufficient.

Because async processing introduces additional thread switches, the request latency may increase slightly, but the overhead is usually minimal compared with the gain in concurrency.

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.

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

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.