Spring MVC has extensive integration of asynchronous request processing on Servlet 3.0:
The
DeferredResultandCallablereturn values in controller methods provide basic support for a single asynchronous return value.Controllers can stream multiple values, including SSE and raw data.
Controllers can use reactive clients and return reactive types to process responses.
DeferredResult
Once the asynchronous request processing feature is enabled in servlet container, controller methods will be able to wrap any supported return value of the controller method using DeferredResult, as shown in the following example:
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// From some other thread...
return deferredResult;
}
// Save deferredResult somewhere...
deferredResult.setResult(result);
@GetMapping("/quotes")
@ResponseBody
fun quotes(): DeferredResult<String> {
val deferredResult = DeferredResult<String>()
// From some other thread...
return deferredResult
}
// Save deferredResult somewhere...
deferredResult.setResult(result)
The controller can receive the return value asynchronously, from another thread - for example, in response to an external event (JMS message), scheduled task, or other event.
Callable
The controller can wrap any supported return value using java.util.concurrent.Callable, as shown in the following example:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
// ...
"someView"
}
The return value can be obtained by executing this task through the configured TaskExecutor.
Processing
Asynchronous processing of servlet requests is briefly described below:
ServletRequestcan be set to asynchronous mode by callingrequest.startAsync(). The main effect is that the servlet (as well as any filters) can complete execution, but the response will remain open so that processing can finish later.Call
request.startAsync()returns anAsyncContextthat can be used to further control asynchronous processing. For example, it provides adispatchmethod, which is similar to the dispatch from the Servlet API, except that it allows the application to resume processing the request on the servlet container thread.ServletRequestprovides access to the currentDispatcherType, which can be used to differentiate between initial request processing, asynchronous dispatch, forwarding, and other dispatcher types.
Processing DeferredResult works as follows:
The controller returns
DeferredResultand stores it in some queue or list in memory where it can be accessed.Spring MVC calls
request.startAsync().Meanwhile,
DispatcherServletand all configured filters exit the request processing thread, but the response remains open.The application sets
DeferredResultfrom some thread, and Spring MVC sends the request back to the servlet container.DispatcherServletis called again and processing resumes using the asynchronously received return value.
The processing of Callable occurs as follows:
The controller returns
Callable.Spring MVC calls
request.startAsync()and passesCallabletoTaskExecutorto be processed in a separate thread.Meanwhile,
DispatcherServletand all filters exit the servlet container thread, but the response remains open.Eventually
Callableproduces a result, and Spring MVC sends the request back to the servlet container to complete processing.DispatcherServletis called again and processing resumes using the asynchronously received return value fromCallable.
To receive For more information and context, please also see blog posts that introduced support for asynchronous request handling for Spring MVC 3.2.
Exception Handling
If you are using DeferredResult, you can choose whether to call setResult or setErrorResult with an exception. In both cases, Spring MVC sends the request back to the servlet container to complete processing. It is then treated either as if the controller method had returned the specified value, or as if it had thrown the specified exception. The exception then goes through the normal exception handling mechanism (for example, calling methods annotated with @ExceptionHandler).
When Callable is used, similar processing logic occurs, but the main difference is whether the result is returned from the Callable or whether it throws an exception.
Interception
HandlerInterceptor instances can have type AsyncHandlerInterceptor to receive an afterConcurrentHandlingStarted callback on the initial request that triggers asynchronous processing (instead of postHandle and afterCompletion).
HandlerInterceptor implementations may also register CallableProcessingInterceptor or DeferredResultProcessingInterceptor for deeper integration with the asynchronous request lifecycle (for example, to handle a timeout event). For more information, see the section on AsyncHandlerInterceptor.
DeferredResult provides onTimeout(Runnable) and onCompletion(Runnable) callbacks. See details in javadoc by DeferredResult. Callable can be replaced with WebAsyncTask, which exposes additional methods for timeout and completion callback.
Compared to WebFlux
The Servlet API was originally designed to perform a single pass along the filter-servlet chain. Asynchronous request processing, added in Servlet 3.0, allows applications to break out of the filter-servlet chain but leave the response open for further processing. Spring MVC's async support is built around this mechanism. When the controller returns DeferredResult, the filter-servlet chain completes execution and the servlet container thread is released. Later, when DeferredResult is set, a ASYNC send is performed (to the same URL), during which the controller matches again, but instead of calling the value of DeferredResult (as if the controller had returned it) to resume processing.
In contrast, Spring WebFlux is not built on the Servlet API, and it does not need this asynchronous request processing feature since it is asynchronous by definition. Asynchronous processing is built into all framework contracts and is inherently supported at all stages of request processing.
From a programming model perspective, both Spring MVC and Spring WebFlux support asynchronous and reactive types as return values in controller methods. Spring MVC even supports threading, including reactive backlash. However, individual writes in response remain blocking (and are executed in a separate thread), unlike WebFlux, which relies on non-blocking I/O and does not need an additional thread for each write.
Another fundamental difference is that Spring MVC does not support asynchronous or reactive types in controller method arguments (for example, @RequestBody, @RequestPart and others), and also does not have explicit support for asynchronous and reactive types as model attributes. Spring WebFlux supports all of the above.
HTTP Streaming
You can use DeferredResult and Callable for a single asynchronous return value. What if you need to receive multiple asynchronous values and write them back? This section describes how to do this.
Objects
You can use the return value of the ResponseBodyEmitter to create a stream of objects, where each object is serialized using HttpMessageConverter and is written to the response, as shown in the following example:
@GetMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the sender (emitter) somewhere...
return emitter;
}
// In another thread
emitter.send("Hello once");
// and then again
emitter.send("Hello again");
// and at some point will be ready
emitter.complete();
@GetMapping( "/events")
fun handle() = ResponseBodyEmitter().apply {
// Save the sender (emitter) somewhere...
}
// In another thread
emitter.send("Hello once")
// and then emitter again
.send("Hello again")
// and at some point will be ready
emitter.complete()
You can also use ResponseBodyEmitter as the body in the ResponseEntity, which will allow you to customize the response status and headers.
When the emitter throws IOException ( for example, if the remote client has disappeared), applications are not responsible for cleaning up the connection and should not call emitter.complete or emitter.completeWithError. Instead, the servlet container automatically triggers an AsyncListener error notification in which Spring MVC makes a call to completeWithError. This call in turn makes the final ASYNC dispatch to the application, during which Spring MVC calls the configured exception handlers and completes the request.
SSE
SseEmitter (a subclass of ResponseBodyEmitter) provides support for Server-Sent Events if the events sent by the server are formatted according to the W3C SSE specification. To create an SSE stream from a controller, return a SseEmitter as shown in the following example:
@GetMapping(path="/events ", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
SseEmitter emitter = new SseEmitter();
// Save the sender (emitter) somewhere...
return emitter;
}
// In another thread
emitter.send("Hello once");
// and then again
emitter.send("Hello again");
// and at some point will be ready
emitter.complete();
@GetMapping( "/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun handle() = SseEmitter().apply {
// Save the sender (emitter) somewhere...
}
// In another thread
emitter.send("Hello once ")
// and then again
emitter.send("Hello again")
// and at some point will be ready
emitter.complete()
While SSE is the primary option for streaming to browsers, note that Internet Explorer does not support Server-Sent Events. Consider using Spring's WebSocket protocol messaging with fallback mechanisms from the SockJS protocol (including SSE), which are designed for a wide range of browsers.
Raw Data
Sometimes it's useful to bypass the conversion messages and stream directly to the response OutputStream (for example, when downloading a file). To do this, you can use the StreamingResponseBody return type, as shown in the following example:
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
@GetMapping("/download")
fun handle() = StreamingResponseBody {
// write ...
}
You can use StreamingResponseBody as the body in ResponseEntity to configure the status and response headers.
Reactive types
Spring MVC supports the use of reactive client libraries in the controller. These libraries include WebClient from spring-webflux and others such as the Spring Data project's reactive data repositories. In such scenarios, it is convenient to be able to return reactive types from a controller method.
Reactive return values are handled as follows:
A single-value promise is adjusted as if using
DeferredResult. Examples includeMono(Reactor) orSingle(RxJava).Multi-valued stream with streaming media type data (for example,
application/x-ndjsonortext/event-stream) is adjusted, as when usingResponseBodyEmitterorSseEmitter. Examples areFlux(Reactor) orObservable(RxJava). Applications can also returnFlux<ServerSentEvent>orObservable<ServerSentEvent>.Multi-valued stream with any other media type data (for example,
application/json) is adjusted, as when usingDeferredResult<List<?>>.
ReactiveAdapterRegistry from
spring-core, allowing it to adapt when moving between multiple reactive libraries.
Reactive backlash is supported for streaming data to the response, but writing to the response is still blocked and executed in a separate thread via a configured TaskExecutor to avoid blocking the upstream thread's source (for example, Flux returned from WebClient). The default for blocking entry is SimpleAsyncTaskExecutor, but this is not suitable for loading. If you plan to use a stream with a reactive type, then you should use MVC configuration to configure the executor.
Connection Lost
The Servlet API does not send any notification when the remote client goes missing. Therefore, when streaming a response, whether through SseEmitter or reactive types, it is important to send data periodically, since writing will not happen if the client has disconnected. The sending could take the form of an empty (comments only) SSE event or any other data that the other party would have to interpret as a heartbeat message and ignore.
You can also use web messaging solutions ( for example, STOMP over WebSocket or WebSocket with SockJS), which have a built-in mechanism for transmitting heartbeat messages.
Configuration
The asynchronous request processing feature must be enabled at the servlet container level. The MVC configuration also opens up several options for asynchronous requests.
Servlet container
Filter and servlet declarations have an asyncSupported flag that must be set to true to enable asynchronous request processing. Additionally, filter mappings must be declared to handle ASYNC javax.servlet.DispatchType.
In Java configuration, when you use AbstractAnnotationConfigDispatcherServletInitializer to initialize the servlet container , this happens automatically.
In the web.xml configuration, you can add <async-supported>true</async-supported> to DispatcherServlet and to Filter declarations, and add <dispatcher>ASYNC</dispatcher> to filter mappings.
Spring MVC
The MVC configuration provides the following options related to asynchronous request processing:
Java configuration: Use the
configureAsyncSupportcallback for theWebMvcConfigurer.XML namespace: Use the
<async-support>element in the<mvc:annotation-driven>section.
The following parameters can be configured:
The default timeout value for asynchronous requests, which , if not set, depends on the underlying servlet container.
AsyncTaskExecutorto be used for write blocking purposes when streaming data using reactive types and for executingCallableinstances returned from controller methods. We strongly recommend configuring this property if you are working with reactive types or have controller methods that returnCallable, since the default isSimpleAsyncTaskExecutor.Implementations of DeferredResultProcessingInterceptorand implementations ofCallableProcessingInterceptor.
Note that it is also possible to set the timeout value by default for DeferredResult, ResponseBodyEmitter and SseEmitter. For Callable you can use WebAsyncTask to specify the timeout value.
GO TO FULL VERSION