1. Introduction
In this tutorial, we'll explore the @Async annotation in Spring MVC, and then we'll get familiar with Spring WebFlux. Our goal is to have a better understanding of the difference between these two.
2. Implementation Scenario
Here, we want to choose a scenario to show how we can implement a simple web application with each of these APIs. Moreover, we're especially interested to see more about thread management and blocking or non-blocking I/O in each case.
Let's choose a web application with one endpoint that returns a string result. The point here is that the request will pass through a Filter with a small 200ms delay, and then the Controller needs 500ms to calculate and return the result.
Next, we're going to simulate a load with Apache ab on both endpoints and monitor our app behavior with JConsole.
It may worth mentioning that in this article, our goal is not a benchmark between these two APIs, just a small load test so we can trace the thread management.
3. Spring MVC Async
Spring 3.0 introduced the @Async annotation. @Async‘s goal is to allow the application to run heavy-load jobs on a separate thread. Also, the caller can wait for the result if interested. Hence the return type must not be void, and it be can be any of Future, CompletableFuture, or ListenableFuture.
Moreover, Spring 3.2 introduced the org.springframework.web.context.request.async package that, together with Servlet 3.0, brings the joy of the asynchronous process to the web layer. Thus, since Spring 3.2, @Async can be used in classes annotated as @Controller or @RestController.
When the client initiates a request, it goes through all matched filters in the filter chain until it arrives at the DispatcherServlet instance.
Then, the servlet takes care of the async dispatching of the request. It marks the request as started by calling AsyncWebRequest#startAsync, transfers the request handling to an instance of WebSyncManager, and finishes its job without committing the response. The filter chain also is traversed in the reverse direction to the root.
WebAsyncManager submits the request processing job in its associated ExecutorService. Whenever the result is ready, it notifies DispatcherServlet for returning the response to the client.
4. Spring Async Implementation
Let's start the implementation by writing our application class, AsyncVsWebFluxApp. Here, @EnableAsync does the magic of enabling async for our Spring Boot application:
@SpringBootApplication
@EnableAsync
public class AsyncVsWebFluxApp {
public static void main(String[] args) {
SpringApplication.run(AsyncVsWebFluxApp.class, args);
}
}
Then we have AsyncFilter, which implements javax.servlet.Filter. Don't forget to simulate the delay in the doFilter method:
@Component
public class AsyncFilter implements Filter {
...
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// sleep for 200ms
filterChain.doFilter(servletRequest, servletResponse);
}
}
Finally, we develop our AsyncController with the “/async_result” endpoint:
@RestController
public class AsyncController {
@GetMapping("/async_result")
@Async
public CompletableFuture getResultAsyc(HttpServletRequest request) {
// sleep for 500 ms
return CompletableFuture.completedFuture("Result is ready!");
}
}
Because of the @Async above getResultAsync, this method is executed in a separate thread on the application's default ExecutorService. However, it is possible to set up a specific ExecutorService for our method.
Test time! Let's run the application, install Apache ab, or any tools to simulate the load. Then we can send a bunch of concurrent requests over the “async_result” endpoint. We can execute JConsole and attach it to our java application to monitor the process:
ab -n 1600 -c 40 localhost:8080/async_result
5. Spring WebFlux
Spring 5.0 has introduced WebFlux to support the reactive web in a non-blocking manner. WebFlux is based on the reactor API, just another awesome implementation of the reactive stream.
Spring WebFlux supports reactive backpressure and Servlet 3.1+ with its non-blocking I/O. Hence, it can be run on Netty, Undertow, Jetty, Tomcat, or any Servlet 3.1+ compatible server.
Although all servers don't use the same thread management and concurrency control model, Spring WebFlux will work fine as long as they are supporting non-blocking I/O and reactive backpressure.
Spring WebFlux allows us to decompose the logic in a declarative way with Mono, Flux, and their rich operator sets. Moreover, we can have functional endpoints besides its @Controller annotated ones.
6. Spring WebFlux Implementation
For WebFlux implementation, we go the same path as async. So at first, let's create the AsyncVsWebFluxApp:
@SpringBootApplication
public class AsyncVsWebFluxApp {
public static void main(String[] args) {
SpringApplication.run(AsyncVsWebFluxApp.class, args);
}
}
Then let's write our WebFluxFilter, which implements WebFilter. We'll generate an intentional delay and then pass the request to the filter chain:
@Component
public class WebFluxFilter implements org.springframework.web.server.WebFilter {
@Override
public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
return Mono
.delay(Duration.ofMillis(200))
.then(
webFilterChain.filter(serverWebExchange)
);
}
}
At last, we have our WebFluxController. It exposes an endpoint called “/flux_result” and returns a Mono<String> as the response:
@RestController
public class WebFluxController {
@GetMapping("/flux_result")
public Mono getResult(ServerHttpRequest request) {
return Mono.defer(() -> Mono.just("Result is ready!"))
.delaySubscription(Duration.ofMillis(500));
}
}
For the test, we're taking the same approach as with our async sample application. Here's the sample result for:
ab -n 1600 -c 40 localhost:8080/flux_result
7. What's the Difference?
Spring Async supports Servlet 3.0 specifications, but Spring WebFlux supports Servlet 3.1+. It brings a number of differences:
- Spring Async I/O model during its communication with the client is blocking. It may cause a performance problem with slow clients. On the other hand, Spring WebFlux provides a non-blocking I/O model.
- Reading the request body or request parts is blocking in Spring Async, whiles it is non-blocking in Spring WebFlux.
- In Spring Async, Filters and Servlets are working synchronously, but Spring WebFlux supports full asynchronous communication.
- Spring WebFlux is compatible with wider ranges of Web/Application servers than Spring Async, like Netty, and Undertow.
Moreover, Spring WebFlux supports reactive backpressure, so we have more control over how we should react to fast producers than both Spring MVC Async and Spring MVC.
Spring Flux also has a tangible shift towards functional coding style and declarative API decomposition thanks to Reactor API behind it.
Do all of these items lead us to use Spring WebFlux? Well, Spring Async or even Spring MVC might be the right answer to a lot of projects out there, depending on the desired load scalability or availability of the system.
Regarding scalability, Spring Async gives us better results than synchronous Spring MVC implementation. Spring WebFlux, because of its reactive nature, provides us elasticity and higher availability.
8. Conclusion
In this article, we learned more about Spring Async and Spring WebFlux, then we had a comparison of them theoretically and practically with a basic load test.
As always, complete code for the Async sample and the WebFlux sample are available over GitHub.