In this post, we will explore the DeferredResult class in Spring. We can use the DeferredResult class in Spring MVC
for asynchronous request processing.
Introduction
Spring MVC uses DeferredResult for asynchronous request processing to support and take advantage of Servlet 3.0
. Servlet 3.0 introduced support for the asynchronous processing and same was introduced with Spring 3.2. There are a number of benefits of this technique.
- It helps to unblock worker threads for long-running processes.
- DeferredResult will offload the processing from the HTTP thread to a separate thread.
- This helps in scaling the application during the peak load.
1. Maven Dependencies
We are using Spring Boot for this post. Spring Boot helps to quickly Bootstrap a Spring MVC application. Read our article Creating a Web Application with Spring Boot for the initial setup.
2. Traditional HTTP Call
@RestController
public class BlockingRESTController {
@GetMapping("/blocking-request")
public ResponseEntity < ? > blockHttpRequest() throws InterruptedException {
Thread.sleep(4000);
return ResponseEntity.ok("OK");
}
}
With this traditional approach, our request thread is blocked until the request is processed. In above example, request thread will be blocked for 4000 milliseconds, for this duration, the thread will be in waiting for state and is not available to handle and server any new HTTP request.
This can lead to performance degradation if there are a number of long-running processes in the system. To handle these cases, the recommendation is to use non-blocking thread processing with the help of DeferredResult.
2. Non-Blocking Call using DeferredResult
Spring Provide DeferredResult
option to avoid thread blocking for the long-running processes.
@RestController
public class DeferredResultController {
private static final Logger LOGGER = LoggerFactory.getLogger(DeferredResultController.class);
@GetMapping("/asynchronous-request")
public DeferredResult < ResponseEntity < ? >> asynchronousRequestProcessing(final Model model) {
LOGGER.info("Started processing asynchronous request");
final DeferredResult < ResponseEntity < ? >> deferredResult = new DeferredResult < > ();
/**
* Section to simulate slow running thread blocking process
*/
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.submit(() -> {
LOGGER.info("Processing request in new thread");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
LOGGER.error("InterruptedException while executing the thread {}", e.fillInStackTrace());
}
deferredResult.setResult(ResponseEntity.ok("OK"));
});
return deferredResult;
}
}
In above example, the request is processed in a separate thread. This approach carries the following advantages.
- Http worker threads released immediately by passing on the work to the non HTTP thread.
- The worker threads are able to handle additional HTTP request without waiting for the long-running process to finish the work.
If we execute above controller, following output is visible in the server console.
2018-07-05 22:20:58.712 INFO 11879 --- [nio-8080-exec-1] c.j.d.DeferredResultController : Started processing asynchronous request
2018-07-05 22:20:58.714 INFO 11879 --- [nio-8080-exec-1] c.j.d.DeferredResultController : HTTP Wroker thread is relased.
2018-07-05 22:20:58.714 INFO 11879 --- [Pool-1-worker-1] c.j.d.DeferredResultController : Processing request in new thread
Take a close look at the following output nio-8080-exec-1
and Pool-1-worker-1
3. Callback
The DeferredResult class provide the following three callbacks
- onComletion – block on execute when the request completes.
- onError – Execute code on error.
- onTimeout – Custom code to invoke once timeout occurs
3.1 onCompletion Callback
deferredResult.onCompletion(() -> LOGGER.info("Processing complete"));
The above code block will execute on the successful execution of the request
3.1 onTimeout Callback
@GetMapping("/timeout-request")
public DeferredResult < ResponseEntity < ? >> onTimeoutExample(final Model model) {
LOGGER.info("Started processing asynchronous request");
final DeferredResult < ResponseEntity < ? >> deferredResult = new DeferredResult < ResponseEntity < ? >> (5000 l);
deferredResult.onTimeout(() ->
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
.body("Request timeout.")));
/**
* Section to simulate slow running thread blocking process
*/
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.submit(() -> {
LOGGER.info("Processing request in new thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
LOGGER.error("InterruptedException while executing the thread {}", e.fillInStackTrace());
}
deferredResult.setResult(ResponseEntity.ok("OK"));
});
LOGGER.info("HTTP Wroker thread is relased.");
return deferredResult;
}
In the above code, we are setting HttpStatus.REQUEST_TIMEOUT
for the timeout. If you run above method, “Request timeout” sent to the REST client. In the similar fashion, we can also execute custom code for the onError()
callback.
Summary
In this article, we explore the DeferredResult class in Spring. We covered the process to use the DeferredResult option in your code to scale your application. We discussed how to use DeferredResult in the creation of asynchronous endpoints. The complete source code is available over the GitHub.
Thank you Finally my API run asynchronously
Hello Rishabh,
I am happy that it helped!!