Using Asynchronous Requests and Asynchronous Calls in Spring Boot

This article explains the concepts, implementation methods, and best practices for asynchronous requests and asynchronous method calls in Spring Boot, covering servlet‑based async, Callable, WebAsyncTask, DeferredResult, @EnableAsync/@Async usage, thread‑pool configuration, common pitfalls, and the differences between async requests and async calls.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Using Asynchronous Requests and Asynchronous Calls in Spring Boot

1. Asynchronous Requests in Spring Boot

Async vs Sync : An asynchronous request releases the servlet thread and related resources, reducing server load; the response is delayed until the long‑running processing finishes, allowing higher request throughput.

In one sentence: it increases server throughput for client requests, though in production large concurrency is often handled by load balancers or message queues.

1.1 Implementation Methods

Four common ways to implement async requests:

Method 1 – Servlet Async

@RequestMapping(value = "/email/servletReq", method = GET)
public void servletReq(HttpServletRequest request, HttpServletResponse response) {
    AsyncContext asyncContext = request.startAsync();
    asyncContext.addListener(new AsyncListener() {
        @Override public void onTimeout(AsyncEvent event) throws IOException { System.out.println("Timeout"); }
        @Override public void onStartAsync(AsyncEvent event) throws IOException { System.out.println("Thread start"); }
        @Override public void onError(AsyncEvent event) throws IOException { System.out.println("Error:" + event.getThrowable()); }
        @Override public void onComplete(AsyncEvent event) throws IOException { System.out.println("Complete"); }
    });
    asyncContext.setTimeout(20000);
    asyncContext.start(new Runnable() {
        @Override public void run() {
            try {
                Thread.sleep(10000);
                asyncContext.getResponse().setCharacterEncoding("utf-8");
                asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                asyncContext.getResponse().getWriter().println("This is an async response");
            } catch (Exception e) { System.out.println("Exception:" + e); }
            asyncContext.complete();
        }
    });
    System.out.println("Main thread:" + Thread.currentThread().getName());
}

Method 2 – Callable

@RequestMapping(value = "/email/callableReq", method = GET)
@ResponseBody
public Callable<String> callableReq() {
    System.out.println("Outer thread:" + Thread.currentThread().getName());
    return new Callable<String>() {
        @Override public String call() throws Exception {
            Thread.sleep(10000);
            System.out.println("Inner thread:" + Thread.currentThread().getName());
            return "callable!";
        }
    };
}

@Configuration
public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {
    @Resource private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;
    @Override public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(60 * 1000);
        configurer.setTaskExecutor(myThreadPoolTaskExecutor);
        configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
    }
    @Bean public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
        return new TimeoutCallableProcessingInterceptor();
    }
}

Method 3 – WebAsyncTask

@RequestMapping(value = "/email/webAsyncReq", method = GET)
@ResponseBody
public WebAsyncTask<String> webAsyncReq() {
    System.out.println("Outer thread:" + Thread.currentThread().getName());
    Callable<String> result = () -> {
        System.out.println("Inner thread start:" + Thread.currentThread().getName());
        Thread.sleep(4);
        System.out.println("Inner thread return:" + Thread.currentThread().getName());
        return "success";
    };
    WebAsyncTask<String> wat = new WebAsyncTask<>(3000L, result);
    wat.onTimeout(() -> "Timeout");
    return wat;
}

Method 4 – DeferredResult

@RequestMapping(value = "/email/deferredResultReq", method = GET)
@ResponseBody
public DeferredResult<String> deferredResultReq() {
    System.out.println("Outer thread:" + Thread.currentThread().getName());
    DeferredResult<String> result = new DeferredResult<>(60 * 1000L);
    result.onTimeout(() -> result.setResult("Timeout!"));
    result.onCompletion(() -> System.out.println("Completed"));
    myThreadPoolTaskExecutor.execute(() -> {
        System.out.println("Inner thread:" + Thread.currentThread().getName());
        result.setResult("DeferredResult!!");
    });
    return result;
}

2. Asynchronous Calls in Spring Boot

Enable async method execution by adding @EnableAsync to the main class and annotating target methods with @Async (or @Async("threadPool") for a custom pool).

Common pitfalls:

Calling an @Async method from the same class bypasses the proxy, so the annotation has no effect.

Static or private methods cannot be async.

Without a custom TaskExecutor, Spring uses SimpleAsyncTaskExecutor, which creates a new thread for each task instead of reusing a pool.

Solutions include extracting async methods into a separate Spring‑managed bean, obtaining the proxy via ApplicationContext.getBean(...) or AopContext.currentProxy(), or enabling CGLIB proxy exposure with @EnableAspectJAutoProxy(exposeProxy = true).

@Controller
@RequestMapping("/app")
public class EmailController {
    @Autowired private ApplicationContext applicationContext;
    @RequestMapping(value = "/email/asyncCall", method = GET)
    @ResponseBody
    public Map<String, Object> asyncCall() {
        Map<String, Object> res = new HashMap<>();
        EmailController proxy = applicationContext.getBean(EmailController.class);
        proxy.testAsyncTask();
        res.put("code", 200);
        return res;
    }
    @Async
    public void testAsyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("Async task completed!");
    }
}

3. Difference Between Async Request and Async Call

Async requests are used to relieve server pressure by holding the servlet thread while the long‑running task executes, eventually returning a response to the client; they improve request throughput.

Async calls return immediately to the client, delegating the heavy work to background threads (e.g., logging to Kafka). The client does not wait for the background task to finish.

4. Summary

Both async requests and async calls are essential techniques for handling time‑consuming operations in Spring Boot. Proper configuration of thread pools, understanding proxy mechanics, and choosing the right async model are key to effective implementation.

Future articles will dive deeper into Spring AOP dynamic proxies and related advanced topics.

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.

WebSpringBootAsync
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

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.