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 using setErrorResult()
. - 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:
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:
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:
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:
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.
Java 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.
Similar Reads
Asynchronous Processing in System Design
Asynchronous processing involves handling tasks independently of the main program flow. It allows systems to execute multiple tasks concurrently, improving efficiency and responsiveness. This method is especially beneficial for waiting operations, such as I/O tasks or network requests. In this artic
10 min read
Implementing Round Robin Load Balancing in Spring Boot Microservices
Round Robin Load Balancing is a method of distributing client requests across multiple servers in a sequential manner. It ensures that each server handles an equal number of requests over time. This approach can be particularly useful for evenly distributing the load and avoiding overburdening a sin
7 min read
Implementing Rate Limiting in a Spring Boot Application
Rate limiting is a crucial feature in many web applications to control the amount of incoming and outgoing traffic. By implementing rate limiting, you can prevent server overload, ensure fair usage, and protect against abuse, such as Denial of Service (DoS) attacks. In a Spring Boot application, rat
5 min read
Implementing Secure API Communication in Spring Boot
In modern web applications, securing API communication is important to protect sensitive data and ensuring the integrity of interactions between clients and servers. In this article, we will learn how to implement secure API communication in a Spring Boot application. Securing API communication is c
5 min read
Implementing Request Response in Java Apache Kafka
Apache Kafka is a very powerful distributed event streaming platform that can be used for building real-time pipelines and streaming applications. It is highly scalable, fault-tolerant, and provides high throughput. One of the common patterns used in the Kafka application is the request-response pat
6 min read
Spring MVC - @RequestParam Annotation
The @RequestParam annotation is one of the most commonly used annotations in Spring MVC for handling HTTP request parameters. @RequestParam annotation enables Spring to extract input data that may be passed as a query, form data, or any arbitrary custom data. Key features of @RequestParam annotation
5 min read
Spring MVC - Getting Cryptocurrency Details using REST API
Cryptocurrencies are a hot topic throughout the world. Trading with cryptocurrency got both pros and cons. Nowadays REST APIs are very useful to get much information including cryptocurrencies. In this article, we are going to see about one such REST API call to get cryptocurrency detail. We are goi
5 min read
Spring MVC @SessionAttributes Annotation with Example
In the ever-evolving world of web development, a constant challenge is to seamlessly manage data between requests. Enter Spring MVC's @SessionAttributes annotation, a powerful solution that elegantly manages the session attributes of a Spring application. This markup becomes a lifesaver when certain
3 min read
Modify Request Body Before Reaching Controller in Spring Boot
In Spring Boot applications, efficiently handling HTTP requests is crucial, especially when it comes to modifying or validating the data before it reaches the controller. This might involve tasks such as formatting, logging, adding metadata, or sanitizing input. This article explores how to modify t
6 min read
Spring Boot - Consuming and Producing JSON
Spring Boot is one of the famous frameworks for developing web applications and this framework provides a lot of features like auto-configuration, Spring Integration, Dependency management, and other features are available in Spring Boot framework. In this article, we will explain Spring Boot Consum
3 min read