Oct 28, 2021 by Roxandra Lobontiu
Getting started with Reactive Spring / Spring WebFlux
When and how to use Spring WebFlux
Most of the applications we develop today have to be able to process more requests with fewer resources, be highly responsive, be easy to scale and handle failure easily.
A reactive system meets these demands by being responsive, resilient, elastic and message-driven.
While reactive systems refer to the architecture or high-level design of a system, reactive programming refers to an implementation technique that can be used in a reactive architecture. Reactive programming is driven by events and focuses on the flow of data in a non-blocking, asynchronous way.
Reactive programming is the foundation of Spring WebFlux, an alternative way of developing web applications. Spring WebFlux makes it possible to build reactive applications on the HTTP layer. It is a reactive fully non-blocking, annotation-based web framework built on Project Reactor that supports reactive streams back pressure and runs on non-blocking servers such as Netty, Undertow and Servlet 3.1+ containers. Non-blocking servers are generally based on the event loop model which uses only a small number of threads handling requests. When talking about non-blocking or asynchronous request processing, it means no thread is in a waiting state. Essentially, threads are able to complete their task without waiting for previous tasks to be completed.
If you choose to work with Spring WebFlux all the layers of the stack have to be reactive, from the database to the web server.
Reactive streams and Project Reactor
Spring WebFlux supports the Reactive Stream API, which is a standardized tool for processing asynchronous streams with non-blocking backpressure. Backpressure is a way of dealing with a data stream that may be too large to be reliably processed. In other words, backpressure refers to the ability to request data when the consumer is ready to process them.
Reactive streams have a publisher (producer) — subscriber (consumer) model. The publisher emits an event and a subscriber will read it. In the Reactive Streams API there are four main interfaces:
- Publisher — Emits events to subscribers based on the demands received from its subscribers. A publisher can serve multiple subscribers and it has only one method: subscribe
- Subscriber — Receives events emitted by the Publisher. The subscribe has four methods to deal with the events received: onSubscribe, onNext, onError and onComplete
- Subscription — Represents the relationship between the subscriber and the publisher. It has methods that allow requesting for data request(long n) and to cancel the demand of events cancel()
- Processor — Publisher and subscriber at the same time; rarely used.
Spring WebFlux internally uses Project Reactor and its publisher implementations, Flux and Mono.
- Mono — A publisher that can emit 0 or 1 element.
- Flux — A publisher that can emit 0..N elements.
Mono and Flux offer simple ways of creating streams of data:
After creating a stream, in order for it to emit elements we need to subscribe to it. Nothing will happen until you subscribe to the publisher; the data won’t flow until the subscribe method is called.
Reactor offer many versions of the subscribe method:
subscribe(Consumer<? super T> consumer)
subscribe(@Nullable Consumer<? super T> consumer, Consumer<? super Throwable> errorConsumer)
subscribe(@Nullable Consumer<? super T> consumer, @Nullable Consumer<? super Throwable> errorConsumer, @Nullable Runnable completeConsumer)
By using the .log() method we can observe all stream signals and trace them. If no logging framework is configured, the events are logged to the console.
Reactor offers several operators for working with Flux and Mono objects. Most commonly used are:
- Map — Used to transform the publisher elements to another elements
- FlatMap — Similar to map, but transformation is asynchronous
- FlatMapMany — Mono operator used to transform a Mono into a Flux
- DelayElements — Delays the publishing of each element by a given duration
- Concat — Used to combine publishers’ elements by keeping the sequence of the publishers
- Merge — Used to combine publishers without keeping the publishers’ sequence, instead it interleaves the values
- Zip — Used to combine two or more publishers by waiting on all the sources to emit one element and combining these elements into an output value
To learn more on how to work with Reactor’s data streams, you can practice using the Lite Rx API Hands-on from GitHub.
Building a RestApi with WebFlux
Spring WebFlux supports two programming models:
- Annotation-based reactive components
- Functional routing and handling
RestAPI with annotated controller
The annotation model is practically the same as in Spring MVC, so we can use the existing annotations:
The biggest difference between Spring MVC and Spring WebFlux is how the request and response are handled and the support of reactive types.
Here we have some basic CRUD methods from a REST controller. In the controller we do not need to call subscribe, because Spring internal classes will call it for us at the right time.
As you can see, the repository.findAll() method already returns a Flux. This is because the controller uses a reactive repository. As the name suggests, a reactive repository offers reactive data access. Reactive repository interfaces are quite similar to the traditional repository interfaces; a difference would be that the reactive repositories use reactive types as return type and can accept reactive types as parameters.
RestAPI with functional endpoints
Spring Functional Web Framework was initially created in Spring WebFlux but it was also made available to Spring MVC applications. It is an alternative programming model which uses functions for routing and handling requests.
Router functions evaluate the request and choose the appropriate handler function; they are basically an alternative to the @RequstMapping annotation.
A handler function is a method that gets a ServerRequest argument and returns a Mono of type ServerResponse. It can be written also as a lambda:
The following is an example of basic CRUD operations written as handler functions:
Once we create the handler functions, the next step is to create the router functions. The simplest way to do this is to use the RouterFunctions.route() method. There are several ways of composing multiple router functions together:
- RouterFunction.andRoute(RequestPredicate, HandlerFunction)
- add(RouterFunction) on the RouterFunctions.route() builder
Routes are registered as Spring beans so they can be created in any configuration class.
A Server-Sent-Event is an HTTP standard that allows the client to receive updates whenever the server emits data, basically it allows the client to receive real-time updates once the connection is established. The client initiates the connection by requesting updates and keeps it open until the even stream is closed.
The server-side event has to have the content type “text/event-stream”. In order to create a Server-Side-Event streaming endpoint with WebFlux it is enough to return a Flux and specify the content type as text/event-stream.
The following example will emit a new UserEvent every second.
Here is how the data looks if we call the endpoint from a browser, each second a new UserEvent is printed:
The same can be achieved using a handler function:
WebClient is a fully reactive client for making HTTP requests and was introduced by Spring WebFlux as an alternative to the RestTemplate. It is used for making calls to external APIs or webservices and it can also be used in synchronous workflows.
For creating a WebClient instance we can use the WebClient.create() or the WebClient .builder() methods depending on the settings we want to configure.
Next, we prepare the request by specifying the request method, the path, content type or body. The methods retrieve() and exchange() are used to manage the response by converting it into a publisher or a response entity object. The exchange() method will give you access to the complete response, not only the body. In order to use the response or just print it to the console we need to subscribe to the publisher.
Testing with WebClient
WebTestClient is used for testing WebFlux endpoints and it internally uses a WebClient instance. The WebTestClient can be bound to a real server or to a controller, a router function or an application context.
Once the instance of the WebTestClient is created, methods that are similar to WebClient’s methods can be used to prepare the requests. For performing the request, we need to use the exchange() method. The exchange() method returns a WebTestClient.ResponseSpec object which has some useful methods to verify the response like the expectStatus, expectBody, and expectHeader methods
The following is an example of a simple test to check the results of a get call to retrieve all the users:
When deciding whether or not to use Spring WebFlux, we need to take into account that in order to take advantage of it we need to ensure the entire flow uses reactive and asynchronous programming and none of the operations are blocking. The application will need to run on non-blocking servers, such as Netty, Undertow and Servlet 3.1+ containers and use a reactive database driver. We also need to consider that when processing data we need to use Mono and Flux API operations, which might take some time to get experienced with and can be harder to debug.
Spring WebFlux is a good fit for highly concurrent applications, applications that need to be able to process a large number of requests with as few resources as possible, for applications that need scalability or for applications that need to stream request data in a live manner.