Spring MVC – Implementing Asynchronous Request Processing
In Spring MVC, asynchronous request processing allows the server to handle requests in a non-blocking manner. This approach improves performance by freeing up resources while waiting for responses, which is especially useful for long-running tasks such as remote service calls, file processing, or database queries. Asynchronous processing can enhance server scalability and responsiveness in high-traffic applications.
Implementing Asynchronous Request Processing in Spring MVC
Asynchronous processing in Spring MVC involves handling requests using separate threads, allowing the main thread to continue processing other requests. Key components for asynchronous processing in Spring MVC include DeferredResult
, Callable
, and WebAsyncTask
.
Key Terminologies:
- @Async: A Spring annotation used to mark a method for asynchronous execution. Methods annotated with
@Async
are executed asynchronously in a separate thread, allowing the main thread to continue processing other tasks. - Callable: An interface in Java that represents a task that can be executed asynchronously. In Spring MVC, a method returning a
Callable
allows asynchronous request handling. - DeferredResult: A flexible class that allows the server to return a result later, after a delay. It is often used in event-driven applications where the completion of the task depends on external events or processes.
- WebAsyncTask: A class that extends
Callable
and provides more control over asynchronous tasks, including timeouts, custom thread pools, and task cancellation.
DeferredResult
DeferredResult
in Spring MVC is an abstraction for returning the result of a long-running request without blocking the main processing thread. It decouples the request handling from the actual response, which is provided asynchronously once an event (like a background task or external service call) completes.
When to Use DeferredResult?
Use DeferredResult
when the response depends on multiple asynchronous events, and you want to return the result only when these events are completed.
Example Usage:
@RestController
public class DeferredResultController {
@Autowired
private LongRunningService longRunningService;
@GetMapping("/async-deferred")
public DeferredResult<String> handleDeferredResult() {
DeferredResult<String> deferredResult = new DeferredResult<>();
// Simulate asynchronous task using a separate thread
new Thread(() -> {
try {
String result = longRunningService.process();
deferredResult.setResult(result);
} catch (InterruptedException e) {
deferredResult.setErrorResult("Error occurred while processing.");
}
}).start();
return deferredResult;
}
}
Key Features of DeferredResult:
- The main thread is not blocked while processing the request.
- Results can be set manually using
setResult()
, and errors can be handled usingsetErrorResult()
. - Supports timeouts to handle cases where the response takes too long.
WebAsyncTask
WebAsyncTask
is another way to handle asynchronous requests in Spring MVC. It extends Callable
and offers more control, including timeouts, error handling, and custom thread pools.
When to Use WebAsyncTask?
Use WebAsyncTask
when you need more control over timeouts, error handling, or want to execute tasks in different thread pools.
Example Usage:
@RestController
public class WebAsyncTaskController {
@Autowired
private LongRunningService longRunningService;
@GetMapping("/async-webasynctask")
public WebAsyncTask<String> handleWebAsyncTask() {
Callable<String> callableTask = () -> longRunningService.process();
// Create WebAsyncTask with a timeout and Callable task
WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(6000, callableTask);
// Handle timeout
webAsyncTask.onTimeout(() -> "Task timed out!");
// Handle error
webAsyncTask.onError(() -> "Error occurred while processing the task!");
return webAsyncTask;
}
}
Key Features of WebAsyncTask:
- Allows setting timeouts for long-running tasks.
- Supports error handling and fallback responses.
- Provides better control over thread pools and task management compared to
Callable
.
Callable
Callable
is a simple way to execute long-running tasks asynchronously. When a Callable
is returned from a controller method, Spring processes it in a separate thread and sends the result back when the task completes.
When to Use Callable?
Use Callable
for straightforward asynchronous tasks where the result is returned after a single task completes, and advanced error or timeout handling is not required.
Example Usage:
@RestController
public class CallableController {
@Autowired
private LongRunningService longRunningService;
@GetMapping("/async-callable")
public Callable<String> handleCallable() {
// Return a Callable that simulates a long-running task
return () -> longRunningService.process();
}
}
Key Features of Callable:
- Simplifies execution of asynchronous tasks.
- Automatically returns the result once the
Callable
completes. - No complex configuration is needed, making it ideal for simple use cases.
@Async and Thread Pool Configuration
@Async
@Async
is a Spring annotation that allows methods to be executed asynchronously in a separate thread. This enables background processing without holding up the main thread.
When to Use @Async?
Use @Async
for tasks that can be executed in the background, such as sending emails, making external API calls, or processing large datasets.
Example Usage:
@Service
public class AsyncService {
@Async
public void executeAsyncTask() throws InterruptedException {
Thread.sleep(5000); // Simulate a long-running task
System.out.println("Async task completed.");
}
}
Thread Pool Configuration for @Async
By default, @Async
methods use Spring's simple thread pool executor. For better control, you can configure a custom thread pool to define the number of concurrent threads, maximum threads, queue capacity, and more.
Java Configuration for Custom Thread Pool:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // Minimum number of threads in the pool
executor.setMaxPoolSize(10); // Maximum number of threads
executor.setQueueCapacity(100); // Queue size before rejecting new tasks
executor.setThreadNamePrefix("AsyncTask-");
executor.initialize();
return executor;
}
}
Thread Pool Configuration via application.properties:
# Spring asynchronous configuration
spring.task.execution.pool.core-size=5
spring.task.execution.pool.max-size=10
spring.task.execution.pool.queue-capacity=100
spring.task.execution.thread-name-prefix=async-task-
Configuration Details:
core-size
: Defines the minimum number of threads the pool maintains.max-size
: The maximum number of threads allowed.queue-capacity
: Defines the size of the queue for holding tasks before rejecting them.thread-name-prefix
: Sets the prefix for thread names.
Workflow
- The client sends a request.
- The server delegates the request to the asynchronous thread pool.
- The server returns immediately, without blocking the main thread.
- Once the task is complete, the result is returned to the client.
Implementing Asynchronous Request Processing in Spring MVC
Step 1: Create a New Spring Boot Project
Create a new Spring Boot project using IntelliJ IDEA with the following options:
- Name:
spring-async-example
- Language: Java
- Type: Maven
- Packaging: Jar
Click on the Next button.

Step 2: Add Dependencies
We can add the following dependencies into the Spring Boot Project.
- Spring Web
- Lombok
- Spring Boot DevTools
Click on the Create button.

Project Structure
After project creation done successfully, the folder structure will look like the below image:

Step 3: Configure Application Properties
In the application.properties
file, configure asynchronous request timeout and thread pool settings:
spring.application.name=spring-async-example
# Asynchronous request timeout
spring.mvc.async.request-timeout=60000
# Thread pool configuration for async tasks
spring.task.execution.pool.core-size=5
spring.task.execution.pool.max-size=10
spring.task.execution.pool.queue-capacity=100
spring.task.execution.thread-name-prefix=async-task-
Step 4: Create the LongRunningService Class
Create a service class that simulates a long-running task.
LongRunningService.java:
package com.gfg.springasyncexample;
import org.springframework.stereotype.Service;
@Service
public class LongRunningService {
public String process() throws InterruptedException {
// Simulate a time-consuming process (e.g., database call, API call)
Thread.sleep(5000); // Simulate delay
return "Task completed successfully!";
}
}
Step 5: Create the CallableController Class
Create the CallableController class and this class can be useful when you have the single long-running task that you want to handle the asynchronously. It automatically returns the result once the task is done.
CallableController.java:
package com.gfg.springasyncexample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.Callable;
@RestController
public class CallableController {
@Autowired
private LongRunningService longRunningService;
@GetMapping("/async-callable")
public Callable<String> handleCallable() {
// Return a Callable that simulates a long-running task
return () -> longRunningService.process();
}
}
Step 6: Create the DeferredResultController Class
Create the DeferredResultController class and this class is ideal for handling cases where the response might depend on the multiple asynchronous events or when you need the external events to the trigger the response.
DeferredResultController.java:
package com.gfg.springasyncexample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
@RestController
public class DeferredResultController {
@Autowired
private LongRunningService longRunningService;
@GetMapping("/async-deferred")
public DeferredResult<String> handleDeferredResult() {
DeferredResult<String> deferredResult = new DeferredResult<>();
// Simulate asynchronous task using a separate thread
new Thread(() -> {
try {
String result = longRunningService.process();
deferredResult.setResult(result);
} catch (InterruptedException e) {
deferredResult.setErrorResult("Error occurred while processing.");
}
}).start();
return deferredResult;
}
}
Step 7: Create the WebAsyncTask Class
Create the WebAsyncTask class. WebAsyncTask provides the more control over the execution of the task. It allows us to define the timeouts, thread pools, and handle the errors more gracefully.
WebAsyncTaskController.java:
package com.gfg.springasyncexample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;
import java.util.concurrent.Callable;
@RestController
public class WebAsyncTaskController {
@Autowired
private LongRunningService longRunningService;
@GetMapping("/async-webasynctask")
public WebAsyncTask<String> handleWebAsyncTask() {
Callable<String> callableTask = () -> longRunningService.process();
// Create WebAsyncTask with a timeout and Callable task
WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(6000, callableTask);
// Handle timeout
webAsyncTask.onTimeout(() -> "Task timed out!");
// Handle error
webAsyncTask.onError(() -> "Error occurred while processing the task!");
return webAsyncTask;
}
}
Step 8: Main Application
This is entry point for the Spring Boot application, add the @Async anntotation to enable the asynchronous functionalities in this project.
package com.gfg.springasyncexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class SpringAsyncExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAsyncExampleApplication.class, args);
}
}
Step 9: Run the Application
Now run the application, and it will start at port 8080.

Step 10: Testing the Application
We can test the application using postman tool.
1. Callable Output:
After calling /async-callable, we will see the message after a 5-second delay:
GET http://localhost:8080/async-callable
Response:

2. DeferredResult Output:
After calling /async-deferred, we will see the message after a 5-second delay:
GET http://localhost:8080/async-deferred
Response:

3. WebAsyncTask Output:
After calling /async-webasynctask, we will either see
Task completed successfully!
or, if the task exceeds the timeout limit:
Task timeout occurred!
Response:

The example project demonstrates the asynchronous request processing scenario, demonstrating how to delay the response to the client while handling background tasks. It can be critical in modern web applications where the server needs to handle the multiple requests without being blocked by long-running processes.