Spring Reactive Guide
Spring Reactive Guide
Guide
1: Intro To Reactor Core
1. Introduction 2
3. Maven Dependencies 4
4.1. Flux 5
4.2. Mono 5
5. Subscribing to a Stream 7
6. Backpressure 11
7. Operating on a Stream 13
8. Hot Streams 15
9. Concurrency 17
10. Conclusion 18
2. Debugging Reactive Streams in Java
1. Overview 20
Parameter 25
8. Conclusion 31
3. Dependencies 35
5. Reactive RestController 37
5.1. Single Resource 37
8. Conclusion 43
2. Maven Dependency 46
3.1. HandlerFunction 47
3.2. RouterFunction 48
5. Composing Routes 51
6. Testing Routes 52
7. Conclusion 54
5. Spring 5 WebClient
1. Overview 56
3. Dependencies 58
6. Conclusion 70
3. Comparison Example 75
4. Conclusion 78
3. WebClient Setup 82
7. Conclusion 92
5. Conclusion 102
8. Conclusion 116
1
1. Introduction
In this chapter, we’ll attempt to demystify this paradigm. We’ll take small
steps through Reactor until we’ve illustrated how to compose reactive code,
laying the foundation for more advanced chapters to come in a later series.
2
2. Reactive Streams Specification
In other words, it’s a system where lots of events are being produced and
consumed asynchronously. Think about a stream of thousands of stock
updates per second coming into a financial application, and for it to have to
respond to those updates in a timely manner.
Backpressure means that our consumer should be able to tell the producer
how much data to send in order to prevent this from happening, which is
precisely what’s laid out in the specification.
3
3. Maven Dependencies
1. <dependency>
2. <groupId>io.projectreactor</groupId>
3. <artifactId>reactor-core</artifactId>
4. <version>3.4.16</version>
5. </dependency>
1. <dependency>
2. <groupId>ch.qos.logback</groupId>
3. <artifactId>logback-classic</artifactId>
4. <version>1.2.6</version>
5. </dependency>
4
4. Producing a Stream of Data
This could be something like the stock update example that we gave earlier.
Without this data, we wouldn’t have anything to react to, which is why this is
a logical first step.
4.1. Flux
The first way of doing this is with Flux. It’s a stream that can emit 0..n elements.
Let’s try creating a simple one:
4.2. Mono
The second way of doing this is with a Mono, which is a stream of 0..1 elements.
Let’s try instantiating one:
This looks and behaves almost exactly the same as the Flux, only this time
we’re limited to no more than one element.
5
4.3. Why Not Only Flux?
Before experimenting further, it’s worth highlighting why we have these two
data types.
First, it should be noted that both Flux and Mono are implementations of the
Reactive Streams Publisher interface. Both classes are compliant with the
specification, and we could use this interface in their place:
But really, knowing this cardinality is useful because a few operations only
make sense for one of the two types, and it can be more expressive (imagine
findOne() in a repository).
6
5. Subscribing to a Stream
Let’s use the subscribe() method to collect all the elements in a stream:
The data won’t start flowing until we subscribe. Notice that we added some
logging as well, which will be helpful when we look at what’s happening
behind the scenes.
With logging in place, we can use it to visualize how the data is flowing
through our stream:
7
5.2. The Flow of Elements
First of all, everything is running on the main thread. We won’t go into any
details about this now, as we’ll be taking a further look at concurrency
later on in this chapter.
4. onComplete() – This is called last, after receiving the last element. There’s
actually an onError() as well, which would be called if there’s an exception,
but in this case, there isn’t.
This is the flow laid out in the Subscriber interface as part of the Reactive
Streams Specification. In reality, this is what’s been instantiated behind the
scenes in our call to onSubscribe().
It’s a useful method, but to better understand what’s happening, let’s provide
a Subscriber interface directly:
8
1. Flux.just(1, 2, 3, 4)
2. .log()
3. .subscribe(new Subscriber<Integer>() {
4. @Override
5. public void onSubscribe(Subscription s) {
6. s.request(Long.MAX_VALUE);
7. }
8.
9. @Override
10. public void onNext(Integer integer) {
11. elements.add(integer);
12. }
13.
14. @Override
15. public void onError(Throwable t) {}
16.
17. @Override
18. public void onComplete() {}
19. });
We can see that each possible stage in the above flow maps to a method in
the Subscriber implementation.
It just happens that Flux has provided us with a helper method to reduce
this verbosity.
9
Only we don’t.
The core difference is that Reactive is a push model, whereas the Java 8
Streams are a pull model. In a reactive approach, events are pushed to the
subscribers as they come in.
The next thing to notice is that a Streams terminal operator is just that, a
terminal, pulling all the data and returning a result.
We can also do things like combine streams, throttle streams, and apply
backpressure, which we’ll cover next.
10
6. Backpressure
1. Flux.just(1, 2, 3, 4)
2. .log()
3. .subscribe(new Subscriber<Integer>() {
4. private Subscription s;
5. int onNextAmount;
6.
7. @Override
8. public void onSubscribe(Subscription s) {
9. this.s = s;
10. s.request(2);
11. }
12.
13. @Override
14. public void onNext(Integer integer) {
15. elements.add(integer);
16. onNextAmount++;
17. if (onNextAmount % 2 == 0) {
18. s.request(2);
19. }
20. }
21.
22. @Override
23. public void onError(Throwable t) {}
24.
25. @Override
26. public void onComplete() {}
27. });
11
Now if we run our code again, we’ll see the request(2) is called, followed by
two onNext() calls, and then request(2) again:
12
7. Operating on a Stream
1. Flux.just(1, 2, 3, 4)
2. .log()
3. .map(i -> i * 2)
4. .subscribe(elements::add);
1. Flux.just(1, 2, 3, 4)
2. .log()
3. .map(i -> i * 2)
4. .zipWith(Flux.range(0, Integer.MAX_VALUE),
5. (one, two) -> String.format(“First Flux: %d, Second Flux: %d”,
6. one, two))
7. .subscribe(elements::add);
8.
9. assertThat(elements).containsExactly(
10. “First Flux: 2, Second Flux: 0”,
11. “First Flux: 4, Second Flux: 1”,
12. “First Flux: 6, Second Flux: 2”,
13. “First Flux: 8, Second Flux: 3”);
13
Here we’re creating another Flux that keeps incrementing by one, and
streaming it together with our original one. We can see how these work
together by inspecting the logs:
Note that we now have one subscription per Flux. The onNext() calls are also
alternated, so the index of each element in the stream will match when we
apply the zip() function.
14
8. Hot Streams
Thus far, we’ve focused primarily on cold streams. These are static, fixed-
length streams that are easy to deal with. A more realistic use case for
reactive might be something that happens infinitely.
15
8.1. Creating a ConnectableFlux
One way to create a hot stream is by converting a cold stream into one. Let’s
create a Flux that lasts forever, outputting the results to the console, which
will simulate an infinite stream of data coming from an external resource:
1. publish.subscribe(System.out::println);
2. publish.subscribe(System.out::println);
If we try running this code, nothing will happen. It’s not until we call connect()
that the Flux will start emitting:
1. publish.connect();
9. Concurrency
All of our above examples have currently run on the main thread. However,
we can control which thread our code runs on if we want. The Scheduler
interface provides an abstraction around asynchronous code, for which
many implementations are provided for us. Let’s try subscribing to a different
thread than main:
16
9. Concurrency
1. Flux.just(1, 2, 3, 4)
2. .log()
3. .map(i -> i * 2)
4. .subscribeOn(Schedulers.parallel())
5. .subscribe(elements::add);
Concurrency gets more interesting than this, and it’ll be worth exploring it in
another chapter.
17
10. Conclusion
Later chapters in this series will cover more advanced concurrency and
other reactive concepts. There’s also another chapter covering Reactor with
Spring.
18
2. Debugging Reactive Streams in Java
19
1. Overview
Keeping in mind that Reactive Streams have been gaining popularity over
the last few years, it’s a good idea to know how we can carry out this task
efficiently.
Let’s start by setting up a project using a reactive stack to see why this is
often troublesome.
20
2. Scenario With Bugs
To understand the big picture, we’ll mention that our application will be
consuming and processing streams of simple Foo objects, which contain
only an id, a formattedName, and a quantity field.
Now let’s examine a snippet and the output it generates when an unhandled
error shows up:
After running our application for a few seconds, we can see that it’s logging
exceptions from time to time.
If we take a close look at one of the errors, we’ll find something similar to
this:
21
1. Caused by: java.lang.StringIndexOutOfBoundsException: String index
2. out of range: 15
3. at j.l.String.substring(String.java:1963)
4. at com.baeldung.debugging.consumer.service.FooNameHelper
5. .lambda$1(FooNameHelper.java:38)
6. at r.c.p.FluxMap$MapSubscriber.onNext(FluxMap.java:100)
7. at r.c.p.FluxMap$MapSubscriber.onNext(FluxMap.java:114)
8. at r.c.p.FluxConcatMap$ConcatMapImmediate.
9. innerNext(FluxConcatMap.java:275)
10. at r.c.p.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.
11. java:849)
12. at r.c.p.Operators$MonoSubscriber.complete(Operators.
13. java:1476)
14. at r.c.p.MonoDelayUntil$DelayUntilCoordinator.
15. signal(MonoDelayUntil.java:211)
16. at r.c.p.MonoDelayUntil$DelayUntilTrigger.
17. onComplete(MonoDelayUntil.java:290)
18. at r.c.p.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:118)
19. at r.c.s.SchedulerTask.call(SchedulerTask.java:50)
20. at r.c.s.SchedulerTask.call(SchedulerTask.java:27)
21. at j.u.c.FutureTask.run(FutureTask.java:266)
22. at j.u.c.ScheduledThreadPoolExecutor$ScheduledFutureTask
23. .access$201(ScheduledThreadPoolExecutor.java:180)
24. at j.u.c.ScheduledThreadPoolExecutor$ScheduledFutureTask
25. .run(ScheduledThreadPoolExecutor.java:293)
26. at j.u.c.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.
27. java:1149)
28. at j.u.c.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.
29. java:624)
30. at j.l.Thread.run(Thread.java:748)
Based on the root cause, and noting the FooNameHelper class mentioned in
the stack trace, we can imagine that on some occasions, our Foo objects are
being processed with a formattedName value that’s shorter than expected.
Of course, this is just a simplified case, and the solution seems rather obvious.
But let’s imagine this was a real-case scenario, where the exception itself
doesn’t help us solve the issue without some context information.
22
Did other previous steps affect the formattedName field before arriving at
this stage?
To make things worse, sometimes the exception isn’t even thrown from
within our functionality.
23
3. Using a Debug Session
One option to figure out what’s going on with our application is to start a
debugging session using our favorite IDE.
24
4. Logging Information With the doOnErrorMethod
or Using the Subscribe Parameter
Note: It’s worth mentioning that if we don’t need to carry out further
processing on the subscribe method, we can chain the doOnError
function on our publisher:
1. flux.doOnError(error -> {
2. logger.error(“The following error happened on processFoo
3. method!”, error);
4. }).subscribe();
Now we’ll have some guidance on where the error might be coming
from, even though we still don’t have much information about the actual
element that generated the exception.
25
5. Activating Reactor’s Global Debug Configuration
The Reactor library provides a Hooks class that lets us configure the behavior
of Flux and Mono operators.
Hooks.onOperatorDebug();
After the debug mode is activated, our exception logs will include some
helpful information:
26
As we can see, the first section remains relatively the same, but the following
sections provide information about:
1. The assembly trace of the publisher — here we can confirm that the error
was first generated in the processFoo method.
2. The operators that observed the error after it was first triggered, with the
user class where they were chained.
Note: In this example, mainly to see this clearly, we’re adding the operations
on different classes.
We can toggle the debug mode on or off at any time, but it won’t affect Flux
and Mono objects that have already been instantiated.
One other aspect to keep in mind is that the assembly trace is generated
properly even if there are different threads operating on the stream.
Now if we check the logs, we’ll appreciate that in this case, the first section
might change a little bit, but the last two remain fairly the same.
The first part is the thread stack trace; therefore, it’ll show only the
operations carried out by a particular thread.
As we’ve seen, that’s not the most important section when we’re debugging
the application, so this change is acceptable.
27
6. Activating the Debug Output on a Single Process
Reactor also provides a way to enable the debug mode on single crucial
processes, which is less memory-consuming.
Note that in this manner, the assembly trace will be logged at the checkpoint
stage:
28
We should implement the checkpoint method towards the end of the
reactive chain.
Also, we’ll note that the library offers an overloaded method. We can avoid:
29
7. Logging a Sequence of Elements
Finally, Reactor publishers offer one more method that could potentially
come in handy in some cases. By calling the log method in our reactive
chain, the application will log each element in the flow with the state that
it has at that stage.
We can easily see the state of each Foo object at this stage, and how the
framework cancels the flow when an exception happens,
Of course, this approach is also costly, and we’ll have to use it in moderation.
30
8. Conclusion
This is especially true if we’re not used to handling reactive and asynchronous
data structures, and we need extra help to figure out how things work.
31
3. Guide to Spring 5 WebFlux
32
1. Overview
In this chapter, we’ll create a small reactive REST application using the
reactive web components RestController and WebClient.
We’ll also look at how to secure our reactive endpoints using Spring Security.
33
2. Spring WebFlux Framework
34
3. Dependencies
1. <dependency>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter-webflux</artifactId>
4. <version>2.6.4</version>
5. </dependency>
35
4. Reactive REST Application
36
5. Reactive RestController
1. @RestController
2. @RequestMapping(“/employees”)
3. public class EmployeeController {
4.
5.
6. private final EmployeeRepository employeeRepository;
7.
8. // constructor...
9. }
1. @GetMapping(“/{id}”)
2. private Mono<Employee> getEmployeeById(@PathVariable String id) {
3. return employeeRepository.findEmployeeById(id);
4. }
37
5.2. Collection Resource
We’ll also add an endpoint that publishes the collection resource of all
Employees:
1. @GetMapping
2. private Flux<Employee> getAllEmployees() {
3. return employeeRepository.findAllEmployees();
4. }
For the collection resource, we’ll use a Flux of type Employee since that’s
the publisher for 0..n elements.
38
6. Reactive Web Client
We can use WebClient to create a client to retrieve data from the endpoints
provided by the EmployeeController.
Here we created a WebClient using its factory method, create. It’ll point to
localhost:8080, so we can use relative URLs for calls made by this client
instance.
39
6.2. Retrieving a Collection Resource
40
7. Spring WebFlux Security
1. @PostMapping(“/update”)
2. private Mono<Employee> updateEmployee(@RequestBody Employee
3. employee) {
4. return employeeRepository.updateEmployee(employee);
5. }
Now, to restrict access to this method, we’ll create SecurityConfig and define
some path-based rules to allow only ADMIN users:
1. @EnableWebFluxSecurity
2. public class EmployeeWebSecurityConfig {
3.
4.
5. // ...
6.
7.
8. @Bean
9. public SecurityWebFilterChain springSecurityFilterChain(
10. ServerHttpSecurity http) {
11. http.csrf().disable()
12. .authorizeExchange()
13. .pathMatchers(HttpMethod.POST, “/employees/update”).
14. hasRole(“ADMIN”)
15. .pathMatchers(“/**”).permitAll()
16. .and()
17. .httpBasic();
18. return http.build();
19. }
20. }
41
This configuration will restrict access to the endpoint /employees/update.
Therefore, only users with a role ADMIN will be able to access this endpoint
and update an existing Employee.
42
8. Conclusion
In this chapter, we explored how to create and work with reactive web
components as supported by the Spring WebFlux framework. As an example,
we built a small Reactive REST application.
We also looked into how to create a secured reactive endpoint with the
help of Spring Security.
Finally, the complete source code used in this chapter is available over on
Github.
43
4. Introduction to the Functional Web
Framework in Spring 5
44
1. Introduction
We’ll base this off of our existing guide to Spring 5 WebFlux. In that guide,
we created a simple reactive REST application using annotation-based
components. Here, we’ll use the functional framework instead.
45
2. Maven Dependency
1. <dependency>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter-webflux</artifactId>
4. <version>2.6.4</version>
5. </dependency>
46
3. Functional Web Framework
3.1. HandlerFunction
1. @FunctionalInterface
2. public interface HandlerFunction<T extends ServerResponse> {
3. Mono<T> handle(ServerRequest request);
4. }
47
3.2. RouterFunction
1. @FunctionalInterface
2. public interface RouterFunction<T extends ServerResponse> {
3. Mono<HandlerFunction<T>> route(ServerRequest request);
4. // ...
5. }
48
4. Reactive REST Application Using Functional Web
Now we’ll implement the same logic using router and handler functions.
Routes are registered as Spring beans, and can be created inside any
configuration class.
Let’s create our first route using RouterFunction that publishes a single
Employee resource:
1. @Bean
2. RouterFunction<ServerResponse> getEmployeeByIdRoute() {
3. return route(GET(“/employees/{id}”),
4. req -> ok().body(
5. employeeRepository().findEmployeeById(req.
6. pathVariable(“id”)), Employee.class));
7. }
In other words, the above example routes all the GET requests for the /
employees/{id} to EmployeeRepository#findEmployeeById(String id)
method.
49
4.2. Collection Resource
1. @Bean
2. RouterFunction<ServerResponse> getAllEmployeesRoute() {
3. return route(GET(“/employees”),
4. req -> ok().body(
5. employeeRepository().findAllEmployees(), Employee.class));
6. }
1. @Bean
2. RouterFunction<ServerResponse> updateEmployeeRoute() {
3. return route(POST(“/employees/update”),
4. req -> req.body(toMono(Employee.class))
5. .doOnNext(employeeRepository()::updateEmployee)
6. .then(ok().build()));
7. }
50
5. Composing Routes
1. @Bean
2. RouterFunction<ServerResponse> composedRoutes() {
3. return
4. route(GET(“/employees”),
5. req -> ok().body(
6. employeeRepository().findAllEmployees(), Employee.class))
7.
8. .and(route(GET(“/employees/{id}”),
9. req -> ok().body(
10. employeeRepository().findEmployeeById(req.
11. pathVariable(“id”)), Employee.class)))
12.
13. .and(route(POST(“/employees/update”),
14. req -> req.body(toMono(Employee.class))
15. .doOnNext(employeeRepository()::updateEmployee)
16. .then(ok().build())));
17. }
To run the application, we can either use separate routes, or the single,
composed one that we created above.
51
6. Testing Routes
To do so, we’ll first need to bind the routes using the bindToRouterFunction
method, and then build the test client instance.
1. @Test
2. void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() {
3. WebTestClient client = WebTestClient
4. .bindToRouterFunction(config.getEmployeeByIdRoute())
5. .build();
6.
7.
8. Employee employee = new Employee(“1”, “Employee 1”);
9.
10.
11. given(employeeRepository.findEmployeeById(“1”)).
12. willReturn(Mono.just(employee));
13.
14.
15. client.get()
16. .uri(“/employees/1”)
17. .exchange()
18. .expectStatus()
19. .isOk()
20. .expectBody(Employee.class)
21. .isEqualTo(employee);
22. }
52
And similarly, getAllEmployeesRoute:
1. @Test
2. void whenGetAllEmployees_thenCorrectEmployees() {
3. WebTestClient client = WebTestClient
4. .bindToRouterFunction(config.getAllEmployeesRoute())
5. .build();
6. List<Employee> employees = Arrays.asList(
7. new Employee(“1”, “Employee 1”),
8. new Employee(“2”, “Employee 2”));
9. Flux<Employee> employeeFlux = Flux.fromIterable(employees);
10. given(employeeRepository.findAllEmployees()).
11. willReturn(employeeFlux);
12. client.get()
13. .uri(“/employees”)
14. .exchange()
15. .expectStatus()
16. .isOk()
17. .expectBodyList(Employee.class)
18. .isEqualTo(employees);
19. }
1. @Test
2. void whenUpdateEmployee_thenEmployeeUpdated() {
3. WebTestClient client = WebTestClient
4. .bindToRouterFunction(config.updateEmployeeRoute())
5. .build();
6.
7. Employee employee = new Employee(“1”, “Employee 1 Updated”);
8.
9. client.post()
10. .uri(“/employees/update”)
11. .body(Mono.just(employee), Employee.class)
12. .exchange()
13. .expectStatus()
14. .isOk();
15. verify(employeeRepository).updateEmployee(employee);
16. }
For more details on testing with WebTestClient, please refer to our chapter
on working with WebClient and WebTestClient.
53
7. Conclusion
54
5. Spring 5 WebClient
55
1. Overview
56
2. What Is the WebClient?
Simply put, WebClient is an interface representing the main entry point for
performing web requests.
It was created as part of the Spring Web Reactive module, and will be
replacing the classic RestTemplate in these scenarios. In addition, the new
client is a reactive, non-blocking solution that works over the HTTP/1.1
protocol.
It’s important to note that even though it’s a non-blocking client, and it
belongs to the spring-webflux library, the solution offers support for both
synchronous and asynchronous operations, making it also suitable for
applications running on a Servlet Stack.
57
3. Dependencies
Since we’re using a Spring Boot application, all we need is the spring-boot-
starter-webflux dependency to obtain Spring Framework’s Reactive Web
support.
1. <dependency>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter-webflux</artifactId>
4. </dependency>
With Gradle, we need to add the following entries to the build.gradle file:
1. dependencies {
2. compile ‘org.springframework.boot:spring-boot-starter-webflux’
3. }
58
4. Working With the WebClient
In order to work properly with the client, we’ll need to know how to:
• create an instance
• make a request
• handle the response
There are three options to choose from. The first one is creating a WebClient
object with default settings:
The second option is to initiate a WebClient instance with a given base URI:
The third option (and the most advanced one) is building a client by using
the DefaultWebClientBuilder class, which allows full customization:
59
4.2. Creating a WebClient Instance With Timeouts
Often, the default HTTP timeouts of 30 seconds are too slow for our needs. To
customize this behavior, we can create an HttpClient instance and configure
our WebClient to use it.
We can:
Note that while we can call timeout on our client request as well, this is
a signal timeout, not an HTTP connection, a read/write, or a response
timeout; it’s a timeout for the Mono/Flux publisher.
60
4.3. Preparing a Request – Define the Method
1. UriSpec<RequestBodySpec> uriSpec =
2. client.method(HttpMethod.POST);
61
4.4. Preparing a Request – Define the URL
The next step is to provide a URL. Once again, we have different ways of
doing this.
Or as a java.net.URL instance:
Keep in mind that if we defined a default base URL for the WebClient, this
last method would override this value.
62
4.5. Preparing a Request – Define the Body
Then we can set a request body, content type, length, cookies, or headers if
we need to.
For example, if we want to set a request body, there are a few available
ways. Probably the most common and straightforward option is using the
bodyValue method:
1. RequestHeadersSpec<?> headersSpec =
2. bodySpec.bodyValue(“data”);
Alternatively, we can make use of the BodyInserters utility class. For example,
let’s see how we can fill in the request body using a simple object, as we did
with the bodyValue method:
63
This class also offers other intuitive functions to cover more advanced
scenarios. For instance, if we have to send multipart requests:
All of these methods create a BodyInserter instance that we can then present
as the body of the request.
64
4.6. Preparing a Request – Define the Headers
After we set the body, we can set headers, cookies, and acceptable media
types. Values will be added to those that have already been set when
instantiating the client.
Also, there’s additional support for the most commonly used headers, like
“If-None-Match”, “If-Modified-Since”, “Accept” and “Accept-Charset”.
1. HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
2. .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
3. .acceptCharset(StandardCharsets.UTF_8)
4. .ifNoneMatch(“*”)
5. .ifModifiedSince(ZonedDateTime.now())
6. .retrieve();
The final stage is sending the request and receiving a response. We can
achieve this by using either the exchangeToMono/exchangeToFlux or the
retrieve method.
65
While the retrieve method is the shortest path to fetching a body directly:
66
5. Working With the WebTestClient
The WebTestClient is the main entry point for testing WebFlux server
endpoints. It has a very similar API to the WebClient, and it delegates most of
the work to an internal WebClient instance focusing mainly on providing a test
context. The DefaultWebTestClient class is a single interface implementation.
The client for testing can be bound to a real server, or work with specific
controllers or functions.
67
5.3. Binding to a Web Handler
1. @Autowired
2. private ApplicationContext context;
3.
4. WebTestClient testClient = WebTestClient.
5. bindToApplicationContext(context)
6. .build();
1. @Autowired
2. private Controller controller;
3.
4. WebTestClient testClient = WebTestClient.
5. bindToController(controller).build();
68
5.6. Making a Request
1. WebTestClient
2. .bindToServer()
3. .baseUrl(“http://localhost:8080”)
4. .build()
5. .post()
6. .uri(“/resource”)
7. .exchange()
8. .expectStatus().isCreated()
9. .expectHeader().valueEquals(“Content-Type”, “application/json”)
10. .expectBody().jsonPath(“field”).isEqualTo(“value”);
69
6. Conclusion
All of the code snippets mentioned in the chapter can be found in our GitHub
repository.
70
6. Spring WebClient vs. RestTemplate
71
1. Overview
72
2. Blocking vs Non-Blocking Client
For a long time, Spring has been offering RestTemplate as a web client
abstraction. Under the hood, RestTemplate uses the Java Servlet API, which
is based on the thread-per-request model.
This means that the thread will block until the web client receives the
response. The problem with the blocking code is due to each thread
consuming some amount of memory and CPU cycles.
Let’s consider having a lot of incoming requests, which are waiting for some
slow service needed to produce the result.
Sooner or later, the requests waiting for the results will pile up. Consequently,
the application will create many threads, which will exhaust the thread pool
or occupy all the available memory. We can also experience performance
degradation because of the frequent CPU context (thread) switching.
73
2.2. WebClient Non-Blocking Client
While RestTemplate uses the caller thread for each event (HTTP call),
WebClient will create something like a “task” for each event. Behind the
scenes, the Reactive framework will queue those “tasks,” and execute them
only when the appropriate response is available.
WebClient is part of the Spring WebFlux library. As such, we can also write
client code using a functional, fluent API with reactive types (Mono and
Flux) as a declarative composition.
74
3. Comparison Example
For this chapter, we’ll implement two REST endpoints, one using
RestTemplate and the other using WebClient. Their task is to call another
slow REST web service, which returns a list of tweets.
1. <dependency>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter-webflux</artifactId>
4. </dependency>
1. @GetMapping(“/slow-service-tweets”)
2. private List<Tweet> getAllTweets() {
3. Thread.sleep(2000L); // delay
4. return Arrays.asList(
5. new Tweet(“RestTemplate rules”, “@user1”),
6. new Tweet(“WebClient is better”, “@user2”),
7. new Tweet(“OK, both are useful”, “@user1”));
8. }
75
3.1. Using RestTemplate to Call a Slow Service
Now let’s implement another REST endpoint that will call our slow service
via the web client.
1. @GetMapping(“/tweets-blocking”)
2. public List<Tweet> getTweetsBlocking() {
3. log.info(“Starting BLOCKING Controller!”);
4. final String uri = getSlowServiceUri();
5.
6.
7. RestTemplate restTemplate = new RestTemplate();
8. ResponseEntity<List<Tweet>> response = restTemplate.exchange(
9. uri, HttpMethod.GET, null,
10. new ParameterizedTypeReference<List<Tweet>>(){});
11.
12.
13. List<Tweet> result = response.getBody();
14. result.forEach(tweet -> log.info(tweet.toString()));
15. log.info(“Exiting BLOCKING Controller!”);
16. return result;
17. }
76
3.2. Using WebClient to Call a Slow Service
1. @GetMapping(value = “/tweets-non-blocking”,
2. produces = MediaType.TEXT_EVENT_STREAM_VALUE)
3. public Flux<Tweet> getTweetsNonBlocking() {
4. log.info(“Starting NON-BLOCKING Controller!”);
5. Flux<Tweet> tweetFlux = WebClient.create()
6. .get()
7. .uri(getSlowServiceUri())
8. .retrieve()
9. .bodyToFlux(Tweet.class);
10.
11.
12. tweetFlux.subscribe(tweet -> log.info(tweet.toString()));
13. log.info(“Exiting NON-BLOCKING Controller!”);
14. return tweetFlux;
15. }
In this case, WebClient returns a Flux publisher, and the method execution
gets completed. Once the result is available, the publisher will start emitting
tweets to its subscribers.
Note that a client (in this case, a web browser) calling this /tweets-non-
blocking endpoint will also be subscribed to the returned Flux object.
Note that this endpoint method completed before the response was
received.
77
4. Conclusion
In this chapter, we explored two different ways of using web clients in Spring.
RestTemplate uses Java Servlet API, and is therefore synchronous and
blocking.
All of the code snippets mentioned in the chapter can be found over on
GitHub.
78
7. Spring WebClient Requests With
Parameters
79
1. Overview
In this chapter, we’ll learn how to reactively consume REST API endpoints
with WebClient.
80
2. REST API Endpoints
To start, let’s define a sample REST API with the following GET endpoints:
Here we defined a few different URIs. In just a moment, we’ll figure out how
to build and send each type of URI with WebClient.
Please note that the URIs for getting products by tags and categories contain
arrays as query parameters; however, the syntax differs because there’s no
strict definition of how arrays should be represented in URIs. This primarily
depends on the server-side implementation. Accordingly, we’ll cover both
cases.
81
3. WebClient Setup
First, we’ll need to create an instance of WebClient. For this chapter, we’ll be
using a mocked object to verify that a valid URI is requested.
1. exchangeFunction = mock(ExchangeFunction.class);
2. ClientResponse mockResponse = mock(ClientResponse.class);
3. when(mockResponse.bodyToMono(String.class))
4. .thenReturn(Mono.just(“test”));
5.
6.
7. when(exchangeFunction.exchange(argumentCaptor.capture()))
8. .thenReturn(Mono.just(mockResponse));
9.
10.
11. webClient = WebClient
12. .builder()
13. .baseUrl(“https://example.com/api”)
14. .exchangeFunction(exchangeFunction)
15. .build();
We’ll also pass a base URL that will be prepended to all requests made by
the client.
Finally, to verify that a particular URI has been passed to the underlying
ExchangeFunction instance, we’ll use the following helper method:
82
The WebClientBuilder class has the uri() method that provides the UriBuilder
instance as an argument. Generally, we make an API call in the following
manner:
1. webClient.get()
2. .uri(uriBuilder -> uriBuilder
3. //... building a URI
4. .build())
5. .retrieve()
6. .bodyToMono(String.class)
7. .block();
We’ll use UriBuilder extensively in this guide to construct URIs. It’s worth
noting that we can build a URI using other methods, and then just pass the
generated URI as a String.
83
4. URI Path Component
1. webClient.get()
2. .uri(“/products”)
3. .retrieve()
4. .bodyToMono(String.class)
5. .block();
6.
7. verifyCalledUrl(“/products”);
Next, we’ll take the /products/{id} endpoint and build the corresponding
URI:
1. webClient.get()
2. .uri(“/products”)
.uri(uriBuilder - > uriBuilder
3. .retrieve()
.path(“/products/{id}”)
4. .bodyToMono(String.class)
.build(2))
5. .block();
.retrieve()
6. .bodyToMono(String.class)
7. verifyCalledUrl(“/products”);
.block();
8.
9.
10. verifyCalledUrl(“/products/2”);
From the code above, we can see that the actual segment values are passed
to the build() method.
In a similar way, we can create a URI with multiple path segments for the
/products/{id}/attributes/{attributeId} endpoint:
84
1. webClient.get()
2. .uri(“/products”)
.uri(uriBuilder - > uriBuilder
3. .retrieve()
.path(“/products/{id}/attributes/{attributeId}”)
4. .bodyToMono(String.class)
.build(2, 13))
5. .block();
.retrieve()
6. .bodyToMono(String.class)
7. verifyCalledUrl(“/products”);
.block();
8.
9.
10. verifyCalledUrl(“/products/2/attributes/13”);
A URI can have as many path segments as required, though the final URI
length must not exceed limitations. Finally, we need to remember to keep
the right order of actual segment values passed to the build() method.
85
5. URI Query Parameters
1. webClient.get()
2. .uri(uriBuilder - > uriBuilder
3. .path(“/products/”)
4. .queryParam(“name”, “AndroidPhone”)
5. .queryParam(“color”, “black”)
6. .queryParam(“deliveryDate”, “13/04/2019”)
7. .build())
8. .retrieve()
9. .bodyToMono(String.class)
10. .block();
11.
12.
13. verifyCalledUrl(“/
14. products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019”);
86
1. webClient.get()
2. .uri(uriBuilder - > uriBuilder
3. .path(“/products/”)
4. .queryParam(“name”, “{title}”)
5. .queryParam(“color”, “{authorId}”)
6. .queryParam(“deliveryDate”, “{date}”)
7. .build(“AndroidPhone”, “black”, “13/04/2019”))
8. .retrieve()
9. .bodyToMono(String.class)
10. .block();
11.
12.
13. verifyCalledUrl(“/
14. products/?name=AndroidPhone&color=black&deliveryDate=13%2F04%2F2019”);
Note that there’s one important difference between the two code snippets
above. With attention to the expected URIs, we can see that they’re encoded
differently. Particularly, the slash character ( / ) was escaped in the last
example.
87
5.2. Array Parameters
We might need to pass an array of values, and there aren’t strict rules for
passing arrays in a query string. Therefore, an array representation in a query
string differs from project to project, and usually depends on underlying
frameworks. We’ll cover the most widely used formats in this chapter.
1. webClient.get()
2. .uri(uriBuilder - > uriBuilder
3. .path(“/products/”)
4. .queryParam(“tag[]”, “Snapdragon”, “NFC”)
5. .build())
6. .retrieve()
7. .bodyToMono(String.class)
8. .block();
9.
10. verifyCalledUrl(“/products/?tag%5B%5D=Snapdragon&tag%5B%5D=NFC”);
As we can see, the final URI contains multiple tag parameters, followed
by encoded square brackets. The queryParam() method accepts variable
arguments as values, so there’s no need to call the method several times.
Alternatively, we can omit square brackets and just pass multiple query
parameters with the same key, but different values,
/products/?category={category1}&category={category2}:
1. webClient.get()
2. .uri(uriBuilder - > uriBuilder
3. .path(“/products/”)
4. .queryParam(“category”, “Phones”, “Tablets”)
5. .build())
6. .retrieve()
7. .bodyToMono(String.class)
8. .block();
9.
10.
11. verifyCalledUrl(“/products/?category=Phones&category=Tablets”);
88
Finally, there’s one more extensively-used method to encode an array, which
is to pass comma-separated values. Let’s transform our previous example
into comma-separated values:
1. webClient.get()
2. .uri(uriBuilder - > uriBuilder
3. .path(“/products/”)
4. .queryParam(“category”, String.join(“,”, “Phones”, “Tablets”))
.build())
5.
.retrieve()
6. .bodyToMono(String.class)
7. .block();
8.
9.
10. verifyCalledUrl(“/products/?category=Phones,Tablets”);
We’re simply using the join() method of the String class to create a comma-
separated string. We can also use any other delimiter that’s expected by the
application.
89
6. Encoding Mode
If the default behavior doesn’t fit our requirements, we can change it.
We need to provide a UriBuilderFactory implementation while building a
WebClient instance. In this case, we’ll use the DefaultUriBuilderFactory class.
To set encoding, we’ll call the setEncodingMode() method. The following
modes are available:
90
1. DefaultUriBuilderFactory factory = new
2. DefaultUriBuilderFactory(BASE_URL);
3. factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_
4. COMPONENT);
5. webClient = WebClient
6. .builder()
7. .uriBuilderFactory(factory)
8. .baseUrl(BASE_URL)
9. .exchangeFunction(exchangeFunction)
10. .build();
91
7. Conclusion
Along the way, we covered various types and formats of query parameters.
Finally, we wrapped up by changing the default encoding mode of the URL
builder.
As always, all of the code snippets from the chapter are available over on
GitHub repository.
92
8. Handling Errors in Spring WebFlux
93
1. Overview
In this chapter, we’ll explore various strategies available for handling errors
in a Spring WebFlux project while walking through a practical example.
We’ll also point out where it might be advantageous to use one strategy
over another, and provide a link to the full source code at the end.
94
2. Setting Up the Example
The Maven setup is the same as our previous chapter, which provides an
introduction to Spring WebFlux.
For our example, we’ll use a RESTful endpoint that takes a username as a
query parameter, and returns “Hello username” as a result.
First, we’ll create a router function that routes the /hello request to a method
named handleRequest in the passed-in handler:
1. @Bean
2. public RouterFunction<ServerResponse> routeRequest(Handler
3. handler) {
4. return RouterFunctions.route(RequestPredicates.GET(“/hello”)
5. .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
6. handler::handleRequest);
7. }
Next, we’ll define the handleRequest() method that calls the sayHello()
method, and finds a way of including/returning its result in the
ServerResponse body:
95
As long as a username is present as part of our request, e.g., if the endpoint
is called as “/hello?username=Tonni”, this endpoint will always function
correctly.
Below, we’ll look at where and how we can reorganize our code to handle
this exception in WebFlux.
96
3. Handling Errors at a Functional Level
There are two key operators built into the Mono and Flux APIs to handle
errors at a functional level.
There are three ways that we can use onErrorResume to handle errors:
97
1. public Mono<ServerResponse> handleRequest(ServerRequest request)
2. {
3. return sayHello(request)
4. .flatMap(s -> ServerResponse.ok()
5. .contentType(MediaType.TEXT_PLAIN)
6. .bodyValue(s))
7. .onErrorResume(e -> Mono.just(“Error “ + e.getMessage())
8. .flatMap(s -> ServerResponse.ok()
9. .contentType(MediaType.TEXT_PLAIN)
10. .bodyValue(s)));
11. }
98
1. public Mono<ServerResponse> handleRequest(ServerRequest request)
2. {
3. return ServerResponse.ok()
4. .body(sayHello(request)
5. .onErrorResume(e -> Mono.error(new NameRequiredException(
6. HttpStatus.BAD_REQUEST,
7. “username is required”, e))), String.class);
8. }
99
4. Handling Errors at a Global Level
So far, all of the examples we’ve presented have tackled error handling at a
functional level.
100
1. @Component
2. @Order(-2)
3. public class GlobalErrorWebExceptionHandler extends
4. AbstractErrorWebExceptionHandler {
5.
6. // constructors
7.
8. @Override
9. protected RouterFunction<ServerResponse> getRoutingFunction(
10 ErrorAttributes errorAttributes) {
11. return RouterFunctions.route(
12. RequestPredicates.all(), this::renderErrorResponse);
13. }
14.
15. private Mono<ServerResponse> renderErrorResponse(
16. ServerRequest request) {
17. Map<String, Object> errorPropertiesMap =
18. getErrorAttributes(request,
19. ErrorAttributeOptions.defaults());
20. return ServerResponse.status(HttpStatus.BAD_REQUEST)
21. .contentType(MediaType.APPLICATION_JSON)
22. .body(BodyInserters.fromValue(errorPropertiesMap));
23. }
24. }
In this example, we set the order of our global error handler to -2. This is to
give it a higher priority than the DefaultErrorWebExceptionHandler, which
is registered at @Order(-1).
The errorAttributes object will be the exact copy of the one that we pass in the
Web Exception Handler’s constructor. This should ideally be our customized
Error Attributes class.
Then we’re clearly stating that we want to route all error handling requests
to the renderErrorResponse() method.
Finally, we’ll get the error attributes and insert them inside a server response
body.
This then produces a JSON response with details of the error, the HTTP status,
and the exception message for machine clients. For browser clients, it has
a “white-label” error handler that renders the same data in HTML format. Of
course, this can be customized.
101
5. Conclusion
As promised, the full source code that accompanies the chapter is available
over on GitHub.
102
9. Spring Security 5 for Reactive
Applications
103
1. Introduction
In this chapter, we’ll explore the new features of the Spring Security 5
framework for securing reactive applications. This release is aligned with
Spring 5 and Spring Boot 2.
104
2. Maven Setup
We’ll use Spring Boot starters to bootstrap our project together with all the
required dependencies.
The basic setup requires a parent declaration, web starter, and security
starter dependencies. We’ll also need the Spring Security test framework:
1. <parent>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter-parent</artifactId>
4. <version>2.6.1</version>
5. <relativePath/>
6. </parent>
7.
8.
9. <dependencies>
10 <dependency>
11. <groupId>org.springframework.boot</groupId>
12. <artifactId>spring-boot-starter-webflux</artifactId>
13. </dependency>
14. <dependency>
15. <groupId>org.springframework.boot</groupId>
16. <artifactId>spring-boot-starter-security</artifactId>
17. </dependency>
18. <dependency>
19. <groupId>org.springframework.security</groupId>
20. <artifactId>spring-security-test</artifactId>
21. <scope>test</scope>
22. </dependency>
23. </dependencies>
We can check out the current version of the Spring Boot security starter
over at Maven Central.
105
3. Project Setup
1. @ComponentScan(basePackages = {“com.baeldung.security”})
2. @EnableWebFlux
3. public class SpringSecurity5Application {
4.
5.
6. public static void main(String[] args) {
7. try (AnnotationConfigApplicationContext context
8. = new AnnotationConfigApplicationContext(
9. SpringSecurity5Application.class)) {
10.
11. context.getBean(NettyContext.class).onClose().block();
12. }
13. }
Here we’ll create a new application context, and wait for Netty to shut down
by calling the .onClose().block() chain on the Netty context.
After Netty is shut down, the context will be automatically closed using the
try-with-resources block.
We’ll also need to create a Netty-based HTTP server, a handler for the HTTP
requests, and the adapter between the server and the handler:
106
1. @Bean
2. public NettyContext nettyContext(ApplicationContext context) {
3. HttpHandler handler = WebHttpHandlerBuilder
4. .applicationContext(context).build();
5. ReactorHttpHandlerAdapter adapter
6. = new ReactorHttpHandlerAdapter(handler);
7. HttpServer httpServer = HttpServer.create(“localhost”, 8080);
8. return httpServer.newHandler(adapter).block();
9. }
For our basic Spring Security configuration, we’ll create a configuration class,
SecurityConfig.
1. @EnableWebFluxSecurity
2. public class SecurityConfig {
3. // ...
4. }
107
1. @Bean
2. public SecurityWebFilterChain securityWebFilterChain(
3. ServerHttpSecurity http) {
4. return http.authorizeExchange()
5. .anyExchange().authenticated()
6. .and().build();
7. }
Also, we’ll need a user details service. Spring Security provides us with a
convenient mock user builder and an in-memory implementation of the
user details service:
1. @Bean
2. public MapReactiveUserDetailsService userDetailsService() {
3. UserDetails user = User
4. .withUsername(“user”)
5. .password(passwordEncoder().encode(“password”))
6. .roles(“USER”)
7. .build();
8. return new MapReactiveUserDetailsService(user);
9. }
Since we’re in reactive land, the user details service should also be reactive.
If we check out the ReactiveUserDetailsService interface, we’ll see that its
findByUsername method actually returns a Mono publisher:
Now we can run our application and observe a regular HTTP basic
authentication form.
108
4. Styled Login Form
To use the new login form, let’s add the corresponding formLogin() builder
method to the ServerHttpSecurity builder:
Now if we open the main page of the application, we’ll see that it looks much
better than the default form we’re used to in previous versions of Spring
Security:
Note that this isn’t a production-ready form, but it’s a good bootstrap of
our application.
109
5. Reactive Controller Security
1. @RestController
2. public class GreetingController {
3.
4.
5. @GetMapping(“/”)
6. public Mono<String> greet(Mono<Principal> principal) {
7. return principal
8. .map(Principal::getName)
9. .map(name -> String.format(“Hello, %s”, name));
10. }
11.
12.
13. }
After logging in, we’ll see the greeting. Then we’ll add another reactive
handler that will be accessible by admin only:
1. @GetMapping(“/admin”)
2. public Mono<String> greetAdmin(Mono<Principal> principal) {
3. return principal
4. .map(Principal::getName)
5. .map(name -> String.format(“Admin access: %s”, name));
6. }
Next, we’ll create a second user with the role ADMIN: in our user details
service:
110
We can now add a matcher rule for the admin URL that requires the user to
have the ROLE_ADMIN authority.
Note that we have to put matchers before the .anyExchange() chain call. This
call applies to all other URLs that were not yet covered by other matchers:
1. return http.authorizeExchange()
2.
3. .pathMatchers(“/admin”).hasAuthority(“ROLE_ADMIN”)
4. .anyExchange().authenticated()
5. .and().formLogin()
6. .and().build();
Now if we log in with user or admin, we’ll see that they both observe the
initial greeting, as we’ve made it accessible for all authenticated users.
111
6. Reactive Method Security
We’ve seen how we can secure the URLs, but what about methods?
1. @EnableWebFluxSecurity
2. @EnableReactiveMethodSecurity
3.
4. public class SecurityConfig {
5. // ...
6. }
Now we’ll create a reactive greeting service with the following content:
1. @Service
2. public class GreetingService {
3.
4.
5. public Mono<String> greet() {
6. return Mono.just(“Hello from service!”);
7. }
8. }
1. @RestController
2. public class GreetingController {
3.
4.
5. private GreetingService greetingService
6.
7.
8. // constructor...
9.
10.
11. @GetMapping(“/greetingService”)
12. public Mono<String> greetingService() {
13. return greetingService.greet();
14. }
} 112
But if we now add the @PreAuthorize annotation on the service method with
the ADMIN role, then the greet service URL won’t be accessible to a regular
user:
1. @Service
2. public class GreetingService {
3.
4.
5. @PreAuthorize(“hasRole(‘ADMIN’)”)
6. public Mono<String> greet() {
7. // ...
8. }
9. }
113
7. Mocking Users in Tests
Let’s check out how easy it is to test our reactive Spring application.
1. @ContextConfiguration(classes = SpringSecurity5Application.class)
2. public class SecurityTest {
3.
4.
5. @Autowired
6. ApplicationContext context;
7.
8.
9. // ...
10. }
Now we’ll set up a simple reactive web test client, which is a feature of the
Spring 5 test framework:
1. @Before
2. public void setup() {
3. this.webTestClient = WebTestClient
4. .bindToApplicationContext(this.context)
5. .configureClient()
6. .build();
7. }
The login and password of this user will be user and password,
respectively, and the role is USER. This, of course, can all be configured
with the @WithMockUser annotation parameters.
Now we can check that the authorized user sees the greeting:
114
1. @Test
2. @WithMockUser
3. void whenHasCredentials_thenSeesGreeting() {
4. webTestClient.get()
5. .uri(“/”)
6. .exchange()
7. .expectStatus().isOk()
8. .expectBody(String.class).isEqualTo(“Hello, user”);
9. }
115
8. Conclusion
As always, the source code for the chapter is available over on GitHub.
116
10. Concurrency in Spring WebFlux
117
1. Introduction
118
2. The Motivation for Reactive Programming
For instance, two user requests to a web server can be handled by different
threads. On a multi-core platform, this has an obvious benefit in terms of
the overall response time. Thus, this model of concurrency is known as the
thread-per-request model:
While thread-based concurrency solves a part of the problem for us, it does
nothing to address the fact that most of our interactions within a single
thread are still blocking. Moreover, the native threads we use to achieve
concurrency in Java come at a significant cost in terms of context switches.
Meanwhile, as web applications face more and more requests, the thread-
per-request model starts to fall short of expectations.
119
3. Concurrency in Reactive Programming
For instance, under the reactive model, a read call to the database doesn’t
block the calling thread while data is fetched. The call immediately returns
a publisher that others can subscribe to. The subscriber can process the
event after it occurs, and may even further generate events itself:
The publisher and subscriber here don’t need to be part of the same thread.
This helps us to achieve better utilization of the available threads, and
therefore, higher overall concurrency.
120
4. Event Loop
• The event loop processes the events from an event queue sequentially,
and returns immediately after registering the callback with the platform.
• The event loop can trigger the callback on the operation completion
notification, and send back the result to the original caller.
121
5. Reactive Programming With Spring WebFlux
Now we have enough insight into reactive programming and its concurrency
model to explore the subject in Spring WebFlux.
As we can see, Spring WebFlux sits parallel to the traditional web framework
in Spring, and doesn’t necessarily replace it.
122
6. Threading Model in Supported Runtimes
While Netty is the default server in a WebFlux application, it’s just a matter
of declaring the right dependency to switch to any other supported server:
1. <dependency>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter-webflux</artifactId>
4. <exclusions>
5. <exclusion>
6. <groupId>org.springframework.boot</groupId>
7. <artifactId>spring-boot-starter-reactor-netty</
8. artifactId>
9. </exclusion>
10. </exclusions>
11. </dependency>
12. <dependency>
13. <groupId>org.springframework.boot</groupId>
14. <artifactId>spring-boot-starter-tomcat</artifactId>
15. </dependency>
While it’s possible to observe the threads created in a Java Virtual Machine
in a number of ways, it’s quite easy to just pull them from the Thread class
itself:
1. Thread.getAllStackTraces()
2. .keySet()
3. .stream()
4. .collect(Collectors.toList());
123
6.1. Reactor Netty
Note that, apart from a normal thread for the server, Netty spawns a bunch
of worker threads for request processing. These are typically available CPU
cores. This is the output on a quad-core machine. We’d also see a bunch
of housekeeping threads typical to a JVM environment, but they aren’t
important here.
Netty uses the event loop model to provide highly scalable concurrency in
a reactive asynchronous manner. Let’s see how Netty implements an event
loop levering Java NIO to provide this scalability:
124
Here, EventLoopGroup manages one or more EventLoop, which must be
continuously running. Therefore, it isn’t recommended to create more
EventLoops than the number of available cores.
WebFlux relies on the Servlet 3.1 API with non-blocking I/O. While it uses
Servlet API behind a low-level adapter, Servlet API isn’t available for direct
usage.
125
The number and type of threads that we can see here are quite different
from what we observed earlier
.
To begin with, Tomcat starts with more worker threads, which defaults to
ten. Of course, we’ll also see some housekeeping threads typical to the JVM,
and the Catalina container, which we can ignore for this discussion.
We need to understand the architecture of Tomcat with Java NIO to correlate
it with the threads we see above.
The point of interest for us here is the threading model that the Connector
component implements to support NIO. It’s comprised of Acceptor, Poller,
and Worker as part of the NioEndpoint module:
Tomcat spawns one or more threads for Acceptor, Poller, and Worker,
typically with a thread pool dedicated to Worker.
126
7. Threading Model in WebClient
As we’ve seen before, reactive applications work with just a few threads, so
there’s no margin for any part of the application to block a thread. Therefore,
WebClient plays a vital role in helping us realize the potential of WebFlux.
1. @GetMapping(“/index”)
2. public Mono<String> getIndex() {
3. return Mono.just(“Hello World!”);
4. }
Then we’ll use WebClient to call this REST endpoint and consume the data
reactively:
1. WebClient.create(“http://localhost:8080/index”).get()
2. .retrieve()
3. .bodyToMono(String.class)
4. .doOnNext(s -> printThreads());
Here we’re also printing the threads that are created using the method we
discussed earlier.
127
7.2. Understanding the Threading Model
If we’re running WebClient on the Reactor Netty, it shares the event loop
that Netty uses for the server. Therefore, in this case, we may not notice
much difference in the threads that are created.
WebClient is also supported on a Servlet 3.1+ container, like Jetty, but the
way it works there is different.
Here, WebClient has to create its event loop. So we can see the fixed number
of processing threads that this event loop creates:
In some cases, having a separate thread pool for client and server can
provide better performance. While it’s not the default behavior with Netty,
it’s always possible to declare a dedicated thread pool for WebClient if
needed.
128
8. Threading Model in Data Access Libraries
There are several databases now that offer reactive libraries for connectivity.
Many of these libraries are available within Spring Data, while we can use
others directly as well.
Setting up support for the reactive repository for MongoDB in a Spring Boot
application is as simple as adding a dependency:
1. <dependency>
2. <groupId>org.springframework.boot</groupId>
3. <artifactId>spring-boot-starter-data-mongodb-reactive</
4. artifactId>
5. </dependency>
This will allow us to create a repository, and use it to perform some basic
operations on MongoDB in a non-blocking manner:
129
1. public interface PersonRepository extends
2. ReactiveMongoRepository<Person, ObjectId> {
3. }
4. .....
5. personRepository.findAll().doOnComplete(this::printThreads);
So what kind of threads can we expect to see when we run this application
on the Netty server?
Well, not surprisingly, we won’t see much difference, as a Spring Data reactive
repository makes use of the same event loop that’s available for the server.
Reactor Kafka is a reactive API for Kafka based on Reactor. Reactor Kafka
enables messages to be published and consumed using functional APIs,
also with non-blocking back-pressure.
1. <dependency>
2. <groupId>io.projectreactor.kafka</groupId>
3. <artifactId>reactor-kafka</artifactId>
4. <version>1.3.10</version>
5. </dependency>
130
1. // producerProps: Map of Standard Kafka Producer Configurations
2. SenderOptions<Integer, String> senderOptions = SenderOptions.
3. create(producerProps);
4. KafkaSender<Integer, String> sender = KafkaSender.
5. create(senderOptions);
6. Flux<SenderRecord<Integer, String, Integer>> outboundFlux = Flux
7. .range(1, 10)
8. .map(i -> SenderRecord.create(new ProducerRecord<>(“reactive-
9. test”, i, “Message_” + i), i));
10. sender.send(outboundFlux).subscribe();
131
We can see a few threads that aren’t typical to the Netty server.
This indicates that Reactor Kafka manages its own thread pool, with a few
worker threads that participate in Kafka message processing exclusively. Of
course, we’ll see a bunch of other threads related to Netty and the JVM that
we can ignore
Kafka consumer, on the other hand, has one thread per consumer group
that blocks to listen for incoming messages. The incoming messages are
then scheduled for processing on a different thread pool.
.
132
9. Scheduling Options in WebFlux
Honestly, the best option would be to just avoid them. However, this may
not always be possible, and we may need a dedicated scheduling strategy
for those parts of our application.
9.1. Reactor
We can use publishOn with a Scheduler anywhere in the chain, with that
Scheduler affecting all the subsequent operators.
133
We can use publishOn with a Scheduler anywhere in the chain, with that
Scheduler affecting all the subsequent operators.
While we can also use subscribeOn with a Scheduler anywhere in the chain,
it will only affect the context of the source of emission.
If we recall, WebClient on Netty shares the same event loop created for the
server as a default behavior. However, we may have valid reasons to create
a dedicated thread pool for WebClient.
Let’s see how we can achieve this in Reactor, which is the default reactive
library in WebFlux:
Earlier, we didn’t observe any difference in the threads created on Netty with
or without WebClient. However, if we now run the code above, we’ll observe
a few new threads being created:
Here we can see the threads created as part of our bounded elastic thread
pool. This is where responses from the WebClient are published once
subscribed.
This leaves the main thread pool for handling the server requests.
134
9.2. RxJava
The default behavior in RxJava isn’t very different from that of the Reactor.
The Observable, and the chain of operators we apply on it, do their work
and notify the observers on the same thread where the subscription was
invoked. Also, RxJava, like Reactor, offers ways to introduce prefixed or
custom scheduling strategies into the chain.
1. <dependency>
2. <groupId>io.reactivex.rxjava2</groupId>
3. <artifactId>rxjava</artifactId>
4. <version>2.2.21</version>
5. </dependency>
135
Then we can start to use RxJava types, like Observable, in our application,
along with RxJava specific Schedulers:
1. io.reactivex.Observable
2. .fromIterable(Arrays.asList(“Tom”, “Sawyer”))
3. .map(s -> s.toUpperCase())
4. .observeOn(io.reactivex.schedulers.Schedulers.trampoline())
5. .doOnComplete(this::printThreads);
Now if we run this application, apart from the regular Netty and JVM related
threads, we should see a few threads related to our RxJava Scheduler:
136
10. Conclusion
Finally, we touched upon the options for controlling the scheduling strategy
in our reactive program within WebFlux.
As always, the source code for this chapter can be found over on GitHub.
137