Open In App

Spring MVC – Implementing Asynchronous Request Processing

Last Updated : 19 Sep, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

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

  1. The client sends a request.
  2. The server delegates the request to the asynchronous thread pool.
  3. The server returns immediately, without blocking the main thread.
  4. 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.

Project Metadata

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.

Add Dependencies

Project Structure

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

Project Folder Structure

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.

Application Runs

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:

Callable Output

2. DeferredResult Output:

After calling /async-deferred, we will see the message after a 5-second delay:

GET http://localhost:8080/async-deferred

Response:

DeferredResult Output


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:

WebAsyncTask Output

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.


Next Article
Article Tags :

Similar Reads