This post will be about how I got started with Spring WebFlux. A few weeks ago, I started to read in detail about Spring WebFlux, Reactive Streams, etc. Looking back at my journey, I did things in the wrong order. In this post I have tried to structure the information, in order to give you a plan for getting started with Spring WebFlux. The examples can be found on GitHub.

Reactive Manifesto

The basis for Reactive Streams can be found in the Reactive Manifesto. It is advised to read this manifesto, it is only 2 pages long, so it won’t take long to read it šŸ˜‰ .

Reactive Streams

Reactive Streams is an initiativeĀ to provide a standard for asynchronous stream processing with non-blocking back pressure, as can be read on the website. This means that a source can send data to a destination without overwhelming the destination with too much data. The destination will tell the source how much data it can handle. This way, resources are used more efficiently. The Reactive Streams specification wants to set a standard also.

The standard for the JVM is available in GitHub. Summarized, the specification consists of the following items:

  • Publisher: the publisher is the source that will send the data to one or more subscribers;
  • Subscriber: a subscriber will subscribe itself to a publisher, will indicate how much data the publisher may send and will process the data;
  • Subscription: on the publisher side, a subscription will be created, which will be shared with a subscriber;
  • Processor: a processor can be used to sit between a publisher and a subscriber, this way a transformation of the data can take place.

The Reactive Streams specification is a standard and since Java 9, it is included in Java with the Flow API. Take a closer look at the Reactive Streams specification and the Java 9 Flow API. As you will notice, the interfaces for Publisher, Subscriber, Subscription and Processor are identical.

Project Reactor

The next step to take in our journey is Project Reactor. This project provides a Reactive library based on the Reactive Streams specification. In other words, an implementation of the specification. Basically, Reactor provides two types:

  • Mono: implements Publisher and returns 0 or 1 elements;
  • Flux: implements Publisher and returns N elements.

But what is its relation with Spring WebFlux? Spring WebFlux is based on Project Reactor and this brings us to the main goal of this post: learn more about Spring WebFlux.

Spring WebFlux

Spring WebFlux is introduced with Spring 5, the official Spring documentation can be found here. You can read the whole documentation, but it is quite some information. I suggest to first look at an example which is being explained and then refer back to the documentation. Important to know is that there are two ways to use Spring WebFlux. One using annotations, which is quite similar to Spring MVC, and one using a functional way. I will use the functional way. Besides that, you must know that Spring MVC and Spring WebFlux exist next to each other. Spring MVC is used for synchronous processing, Spring WebFlux for asynchronous processing.

Spring WebFlux getting started example

To make it easy on myself, I tried to follow the Spring WebFlux getting started example:Ā Building a Reactive RESTful Web Service. I followed the guide and used also Java 9. However, it did not run out-of-the-box. I had to make some adaptations. I will not repeat the steps to follow (you can read it in the guide), but will document my findings when trying to build and run the application. The example can be found in packageĀ com.mydeveloperplanet.myspringwebfluxplanet.greeting.

First, I had to add the Milestones repository to the pom in order to useĀ spring-boot-starter-parent version 2.0.0.RC2.

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.0.RC2</version>
</parent>
...
<repositories>
  <repository>
    <id>spring-milestones</id>
    <name>Spring Milestones</name>
    <url>https://repo.spring.io/milestone</url>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>
</repositories>

<pluginRepositories>
  <pluginRepository>
    <id>spring-milestones</id>
    <name>Spring Milestones</name>
    <url>https://repo.spring.io/milestone</url>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </pluginRepository>
</pluginRepositories>

As of the 1st of March, the above is not necessary anymore since Spring Boot is now generally available. It is now sufficient to only refer to this GA release:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.0.RELEASE</version>
</parent>

Also, in the dependencies, I had to use spring-boot-starter-webflux instead of spring-boot-starter-web.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Because I used Java 9, I had to add the following module-info :

module com.mydeveloperplanet.myspringwebfluxplanet {
  requires reactor.core;
  requires spring.web;
  requires spring.context;
  requires spring.webflux;
  requires spring.boot;
  requires spring.boot.autoconfigure;
}

Now, build and run the example with Maven target spring-boot:run.

In the console, we see that the following line is printed:

>> result = Hello, Spring!

You can also test it in the browser, using the urlĀ http://localhost:8080/hello.

Looking at the console log, we see that the Netty Webserver is being used. This is the default for Spring WebFlux applications.

Spring WebFlux basic example

Now, let’s see if we can create a basic example and understand what is exactly going on here.

We create packageĀ com.mydeveloperplanet.myspringwebfluxplanet.examples and add an ExampleRouter and an ExampleHandler, similar to the Greeting example but we try to do the minimum in order to make it accessible at url http://localhost:8080/example.

The ExampleRouter becomes the following:

@Configuration
public class ExampleRouter {

  @Bean
  public RouterFunction<ServerResponse> route(ExampleHandler exampleHandler) {

    return RouterFunctions
        .route(RequestPredicates.GET("/example").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), exampleHandler::hello);
  }
}

The ExampleHandler becomes the following:

@Component
public class ExampleHandler {

  public Mono<ServerResponse> hello(ServerRequest request) {
    return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
         .body(BodyInserters.fromObject("Hello, Spring Webflux Example!"));
  }
}

Execute the Maven target spring-boot:run. Go to the url http://localhost:8080/example in your browser. The following error is shown:

spring-webflux-routing-error

In the console log, we see the following:

Overriding bean definition for bean 'route' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=exampleRouter; factoryMethodName=route; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mydeveloperplanet/myspringwebfluxplanet/examples/ExampleRouter.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=greetingRouter; factoryMethodName=route; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mydeveloperplanet/myspringwebfluxplanet/greeting/GreetingRouter.class]]

Now change the method route in classĀ ExampleRouter into routeExample and re-run the application. Now the browser shows the following as expected:

Hello, Spring Webflux Example!

As a minimum, we only need a Router and a Handler. We must be sure that the Router Bean has a unique name, otherwise it may be overwritten by another Bean with the same name. The RouterFunction determines the route it matches and eventually resolves to a Handler method.

Spring WebFlux ‘one step further’ example

Now we know how to set up the basics, let’s take it one step further and explore some extra functionality.

We create a methodĀ routeExampleOneStepFurther in classĀ ExampleRouter and chain two routes with each other by means of the andRoute method. This way, we can chain several routes to each other and let them resolve to different handler methods. The routes are evaluated one by one, so you must make sure to define specific routes before more generic routes. The methodĀ routeExampleOneStepFurther is the following:

public RouterFunction<ServerResponse> routeExampleOneStepFurther(ExampleHandler exampleHandler) {

  return RouterFunctions
    .route(RequestPredicates.GET("/exampleFurther1").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), exampleHandler::helloFurther1)
    .andRoute(RequestPredicates.GET("/exampleFurther2").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), exampleHandler::helloFurther2);
 }

The handler methods are created in class ExampleHandler and just return a string:

public Mono<ServerResponse> helloFurther1(ServerRequest request) {
  return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
    .body(BodyInserters.fromObject("Hello, Spring Webflux Example 1!"));
}

public Mono<ServerResponse> helloFurther2(ServerRequest request) {
  return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
    .body(BodyInserters.fromObject("Hello, Spring Webflux Example 2!"));
}

Now build and run the application and invoke the url http://localhost:8080/exampleFurther1. The result is as follows:

Hello, Spring Webflux Example 1!

The same way, url http://localhost:8080/exampleFurther2 shows us the following result:

Hello, Spring Webflux Example 2!

We have tested manually, now lets create a unit test for it. The unit test is the following:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ExampleRouterTest {

  @Autowired
  private WebTestClient webTestClient;

  @Test
  public void testExampleOneStepFurther() {
    webTestClient
      .get().uri("/exampleFurther1")
      .accept(MediaType.TEXT_PLAIN)
      .exchange()
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("Hello, Spring Webflux Example 1!");

    webTestClient
      .get().uri("/exampleFurther2")
      .accept(MediaType.TEXT_PLAIN)
      .exchange()
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("Hello, Spring Webflux Example 2!");
  }

}

The test is based on the Spring WebFlux example. During the test, a server will be started on a random port and will invoke the urls one after each other. TheĀ exchange method gives us access to the response. First the response status is checked and then we check whether the response is correct. In the Spring WebFlux example theĀ equals method is used to verify the response, but this results always in success, even if the response is not equal to the result. This is not correct, the method isEqualTo must be used in order to have a correct test.

Spring WebFlux ‘last steps’ example

Finally, we will create a route which takes a parameter, add a test for it and add a test for a non successful API call.

In our routeExampleOneStepFurther method, we add another route and let it resolve to the method helloFurther3Ā of class ExampleHandler. The route is the following:

.andRoute(RequestPredicates.GET("/exampleFurther3").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), exampleHandler::helloFurther3)

The method helloFurther2 extracts a name parameter and says hello to the given name:

public Mono<ServerResponse> helloFurther3(ServerRequest request) {
  String name = request.queryParam("name").get();
  return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
    .body(BodyInserters.fromObject("Hello, " + name + "!"));
 }

Build and run the application and go to urlĀ http://localhost:8080/exampleFurther3?name=My%20Developer%20Planet. This gives us the expected result:

Hello, My Developer Planet!

The test we add is similar to the previous tests we have written:

@Test
public void testExampleWithParameter() {
  webTestClient
    .get().uri("/exampleFurther3?name=My Developer Planet")
    .accept(MediaType.TEXT_PLAIN)
    .exchange()
    .expectStatus().isOk()
    .expectBody(String.class).isEqualTo("Hello, My Developer Planet!");
}

To conclude with, we add a test which would return a 404 http response. The expected status to an unresolved url path is tested with the isNotFound method:

@Test
public void testExampleWithError() {
  webTestClient
    .get().uri("/example/something")
    .accept(MediaType.TEXT_PLAIN)
    .exchange()
    .expectStatus().isNotFound();
}

Summary

In this post I tried to give some information on how to get started with Spring WebFlux:

  • Suggested reading before getting started with Reactive programming;
  • A basic Spring WebFlux example provided by Spring;
  • A few basic Spring WebFlux examples in order to perform some first steps.

It must be clear that this is only very basic information and from this point on, there is a lot of other things to explore concerning Spring WebFlux.

Advertisement