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.io

We 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.

XML
  <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>
.....
Step-2: Create a HelloController

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.

Java
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$;
  }
}
Java
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);
  }
}
Step-3: Start the Spring-boot app

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.

Step-4: Test the mono and flux endpoints

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.

Java
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.

Java
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.


By |Last Updated: April 2nd, 2024|Categories: Spring Framework|

Table of Contents