Spring WebFlux is a reactive web framework, newly added to Spring 5.x. It is fully non-blocking, supports Reactive Streams back pressure, and runs on such servers as Netty, Undertow, and Servlet 3.1+ containers. The Spring WebFlux uses Project reactor underneath for reactive programming.
1. Why use Spring WebFlux?
An obvious question is, why to use Spring WebFlux when we already have Spring Web-MVC. There are several reasons as discussed below.
- The need of non-blocking web stack to handle concurrency with a small number of threads and scale with fewer hardware resources.
- Support for Functional Programming in web programming.
- There was a need to rewrite the API from scratch as the servlet 3.1 non-blocking I/O API has a lot of limitations.
The term Reactive programming means a programming model that reacts to changes I/O events, controller events, UI events and etc. The reactive programming model is non-blocking as it is based on events and reacts when needed, hence saving a lot of resources.
2. Project reactor in WebFlux
Reactive streams play an important role in WebFlux. Spring uses the Project reactor for the low-level API and provides a high level powerful API on top of it. Spring uses the Mono and Flux from the project reactor. I have already discussed them in the separate articles linked to this tutorial.
In addition to Reactive APIs, WebFlux can also be used with Coroutines APIs in Kotlin which provides a more imperative style of programming.
3. Two Programming models
Spring WebFlux provides a two programming models. We will be discussing each of them in the subsequent articles in details.
- Annotation-based Controllers: You can use the same annotations from the spring-web module. Both Spring MVC and WebFlux controllers support reactive (Reactor and RxJava) return types, and, as a result, it is not easy to tell them apart. One notable difference is that WebFlux also supports reactive
@RequestBody
arguments.
- Functional Endpoints: You can use the Lambda-based, lightweight, and functional programming model. You can think of this as a small library or a set of utilities that an application can use to route and handle requests. The big difference with annotated controllers is that the application is in charge of request handling from start to finish versus declaring intent through annotations and being called back.
We will learn about the Annotation-based reactive REST endpoints in this article and Functional endpoints in next article.
4. Annotation-based reactive REST endpoint
I will use Spring boot for all the examples in Spring WebFlux. For simplicity let us go with the Annotation based Controller. You will find the annotations used in WebFlux endpoints are the same as Spring MVC.
Step-1: Generate the project from start.spring.ioWe will generate our Spring WebFlux project from start.spring.io, with the dependency spring-boot-starter-webflux
. Other dependencies are for testing and ease of development. Use the link to generate the project, unzip it, and import it to your IDE.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
....
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependencies>
.....
Now, we will create a reactive controller named as HelloController
which returns a Mono (Single value) and a Flux (Multi value). Remember, a Mono represents a reactive stream with 0 or 1
values and a Flux emits 0 or N
number of values. I have also shown the AppConfig
class, you will not need to create this class if you generate the project from the provided link.
package c.jbd.webflux.resources;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping(path = "/mono")
public Mono<String> getMono(){
return Mono.just("Welcome to JstoBigdata.com");
}
@GetMapping(path = "/flux", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Flux<String> getFlux(){
Flux<String> msg$ = Flux.just("Welcome ", "to ", "JstoBigdata.com")
.delayElements(Duration.ofSeconds(1)).log();
return msg$;
}
}
package c.jbd.webflux;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AppConfig {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}
Now that we have our sample controller in place, let us navigate inside the project from terminal and run the maven command to start the application.
mvn spring-boot:run
If everything is fine, you should be able to see the app started on localhost:8080
port.
To test the Mono endpoint, open the below url in the browser.
http://localhost:8080/hello/mono
You will be able to get the message, Welcome to JstoBigdata.com
on the browser. There is nothing special here.
Similarly, to test the Flux endpoint, open the below url in browser and observe the output.
http://localhost:8080/hello/flux
You can see that the message gets printed on the browser part by part. This is because our endpoint sends the message as a JSON stream part by part in after each second, with the help of a Flux stream.
5. Test the endpoints using WebClient
Spring WebFlux includes a reactive non-blocking Rest client, known as WebClient for consuming any Http endpoints. We will now write test cases for the endpoints we created in the previous steps. I have discussed the StepVerifier in previous articles on Mono and Flux in the project reactor.
package c.jbd.webflux.resources;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
public class TestHelloController {
private static WebClient webClient;
@BeforeAll
private static void setWebClient() {
webClient = WebClient.create("http://localhost:8080");
}
@Test
public void testMonoEndpoint() {
Mono<String> msg$ = webClient.get()
.uri("/hello/mono")
.retrieve()
.bodyToMono(String.class).log();
StepVerifier.create(msg$)
.expectNext("Welcome to JstoBigdata.com")
.expectComplete();
}
@Test
public void testFluxEndpoint() {
Flux<String> msg$ = webClient.get()
.uri("/hello/flux")
.accept(MediaType.APPLICATION_STREAM_JSON)
.retrieve()
.bodyToFlux(String.class);
StepVerifier.create(msg$)
.expectNext("Welcome to JstoBigdata.com")
.verifyComplete();
}
}
6. Test using WebTestClient
I recommend you to use WebTestClient
to test the webFlux endpoints instead of WebClient. You don’t need the application to be up and running to execute the test cases. You do not need to specify the host url.
package c.jbd.webflux.resources;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
//@SpringJUnitConfig(AppConfig.class)
@WebFluxTest
public class CorrectTestController {
@Autowired
private WebTestClient webTestClient;
private final String TEST_MESSAGE = "Welcome to JstoBigdata.com";
@Test
public void testMonoEndpoint() {
Flux<String> msg$ = webTestClient.get()
.uri("/hello/mono")
.exchange()
.expectStatus().isOk()
.returnResult(String.class).getResponseBody()
.log();
msg$.subscribe(System.out::println);
StepVerifier.create(msg$)
.expectNext(TEST_MESSAGE)
.verifyComplete();
//.expectComplete(); - do not use
}
@Test
public void testFluxEndpoint() {
Flux<String> msg$ = webTestClient.get()
.uri("/hello/flux")
.accept(MediaType.APPLICATION_STREAM_JSON)
.exchange()
.expectStatus().isOk()
.returnResult(String.class).getResponseBody()
.log();
StepVerifier.create(msg$)
.expectNext(TEST_MESSAGE)
.verifyComplete();
}
}
Conclusion
I have given you a brief guide on getting started with Spring WebFlux. We will explore much more in the upcoming articles. It provides better performance with less hardware as compared to the traditional Spring MVC. I would suggest trying this framework to see if this fulfills your project needs. You can download the complete source code from Github.