Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

Spring WebFlux is a new functional web framework built using reactive principles.

In this tutorial, we'll learn how to work with it in practice.

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.

2. Maven Dependency

We'll need the same spring-boot-starter-webflux dependency as defined in the previous article:

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

3. Functional Web Framework

The functional web framework introduces a new programming model where we use functions to route and handle requests.

As opposed to the annotation-based model where we use annotations mappings, here we'll use HandlerFunction and RouterFunctions.

Similarly, as in the annotated controllers, the functional endpoints approach is built on the same reactive stack.

3.1. HandlerFunction

The HandlerFunction represents a function that generates responses for requests routed to them:

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
    Mono<T> handle(ServerRequest request);
}

This interface is primarily a Function<Request, Response<T>>, which behaves very much like a servlet.

Although, compared to a standard Servlet#service(ServletRequest req, ServletResponse res), HandlerFunction doesn't take a response as an input parameter.

3.2. RouterFunction

RouterFunction serves as an alternative to the @RequestMapping annotation. We can use it to route requests to the handler functions:

@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
    Mono<HandlerFunction<T>> route(ServerRequest request);
    // ...
}

Typically, we can import the helper function RouterFunctions.route()  to create routes, instead of writing a complete router function.

It allows us to route requests by applying a RequestPredicate. When the predicate is matched, then the second argument, the handler function, is returned:

public static <T extends ServerResponse> RouterFunction<T> route(
  RequestPredicate predicate,
  HandlerFunction<T> handlerFunction)

Because the route() method returns a RouterFunction, we can chain it to build powerful and complex routing schemes.

4. Reactive REST Application Using Functional Web

In our previous guide, we created a simple EmployeeManagement REST application using @RestController and WebClient.

Now, let's implement the same logic using router and handler functions.

First, we need to create routes using RouterFunction to publish and consume our reactive streams of Employees.

Routes are registered as Spring beans and can be created inside any configuration class.

4.1. Single Resource

Let's create our first route using RouterFunction that publishes a single Employee resource:

@Bean
RouterFunction<ServerResponse> getEmployeeByIdRoute() {
  return route(GET("/employees/{id}"), 
    req -> ok().body(
      employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class));
}

The first argument is a request predicate. Notice how we used a statically imported RequestPredicates.GET method here. The second parameter defines a handler function that'll be used if the predicate applies.

In other words, the above example routes all the GET requests for /employees/{id} to EmployeeRepository#findEmployeeById(String id) method.

4.2. Collection Resource

Next, for publishing a collection resource, let's add another route:

@Bean
RouterFunction<ServerResponse> getAllEmployeesRoute() {
  return route(GET("/employees"), 
    req -> ok().body(
      employeeRepository().findAllEmployees(), Employee.class));
}

4.3. Single Resource Update

Lastly, let's add a route for updating the Employee resource:

@Bean
RouterFunction<ServerResponse> updateEmployeeRoute() {
  return route(POST("/employees/update"), 
    req -> req.body(toMono(Employee.class))
      .doOnNext(employeeRepository()::updateEmployee)
      .then(ok().build()));
}

5. Composing Routes

We can also compose the routes together in a single router function.

Let's see how to combine the routes created above:

@Bean
RouterFunction<ServerResponse> composedRoutes() {
  return 
    route(GET("/employees"), 
      req -> ok().body(
        employeeRepository().findAllEmployees(), Employee.class))
        
    .and(route(GET("/employees/{id}"), 
      req -> ok().body(
        employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)))
        
    .and(route(POST("/employees/update"), 
      req -> req.body(toMono(Employee.class))
        .doOnNext(employeeRepository()::updateEmployee)
        .then(ok().build())));
}

Here, we've used RouterFunction.and() to combine our routes.

Finally, we've implemented the complete REST API needed for our EmployeeManagement application, using routers and handlers.

To run the application, we can either use separate routes or the single, composed one that we created above.

6. Testing Routes

We can use WebTestClient to test our routes.

To do so, we first need to bind the routes using the bindToRouterFunction method and then build the test client instance.

Let's test our getEmployeeByIdRoute:

@Test
public void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() {
    WebTestClient client = WebTestClient
      .bindToRouterFunction(config.getEmployeeByIdRoute())
      .build();

    Employee employee = new Employee("1", "Employee 1");

    given(employeeRepository.findEmployeeById("1")).willReturn(Mono.just(employee));

    client.get()
      .uri("/employees/1")
      .exchange()
      .expectStatus()
      .isOk()
      .expectBody(Employee.class)
      .isEqualTo(employee);
}

and similarly getAllEmployeesRoute:

@Test
public void whenGetAllEmployees_thenCorrectEmployees() {
    WebTestClient client = WebTestClient
      .bindToRouterFunction(config.getAllEmployeesRoute())
      .build();

    List<Employee> employees = Arrays.asList(
      new Employee("1", "Employee 1"),
      new Employee("2", "Employee 2"));

    Flux<Employee> employeeFlux = Flux.fromIterable(employees);
    given(employeeRepository.findAllEmployees()).willReturn(employeeFlux);

    client.get()
      .uri("/employees")
      .exchange()
      .expectStatus()
      .isOk()
      .expectBodyList(Employee.class)
      .isEqualTo(employees);
}

We can also test our updateEmployeeRoute by asserting that our Employee instance is updated via EmployeeRepository:

@Test
public void whenUpdateEmployee_thenEmployeeUpdated() {
    WebTestClient client = WebTestClient
      .bindToRouterFunction(config.updateEmployeeRoute())
      .build();

    Employee employee = new Employee("1", "Employee 1 Updated");

    client.post()
      .uri("/employees/update")
      .body(Mono.just(employee), Employee.class)
      .exchange()
      .expectStatus()
      .isOk();

    verify(employeeRepository).updateEmployee(employee);
}

For more details on testing with WebTestClient please refer to our tutorial on working with WebClient and WebTestClient.

7. Summary

In this tutorial, we introduced the new functional web framework in Spring 5 and looked into its two core interfaces – RouterFunction and HandlerFunction. We also learned how to create various routes to handle the request and send the response.

Additionally, we recreated our EmployeeManagement application introduced in guide to Spring 5 WebFlux with the functional endpoints model.

As always, the full source code can be found over on Github.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
12 Comments
Oldest
Newest
Inline Feedbacks
View all comments
James Parsons
James Parsons
3 years ago

So are we saying goodbye to the traditional Spring MVC?

Grzegorz Piwowarek
Grzegorz Piwowarek
3 years ago
Reply to  James Parsons

Well, the “saying goodbye” process started when the REST model started gaining more popularity.. This is just a new, interesting tool and not a new paradigm

making
making
3 years ago

It would be better to use `flatMap` instead of `map(formData -> blablabla().block())`

“`
RouterFunction router = route(POST(“/login”), request ->
request.body(toFormData()).map(MultiValueMap::toSingleValueMap)
.flatMap(formData -> {
if (“baeldung”.equals(formData.get(“user”)) &&
“you_know_what_to_do”.equals(formData.get(“token”))) {

return ServerResponse.ok()
.body(Mono.just(“welcome back!”), String.class);
}

return badRequest().build();
}));
“`

Grzegorz Piwowarek
Grzegorz Piwowarek
3 years ago
Reply to  making

I have a better idea:

return request
.body(toFormData())
.map(MultiValueMap::toSingleValueMap)
.filter(formData -> "baeldung".equals(formData.get("user")))
.filter(formData -> "you_know_what_to_do".equals(formData.get("token")))
.flatMap(formData -> ok().body(Mono.just("welcome back!"), String.class))
.switchIfEmpty(ServerResponse.badRequest().build());

What do you think?

orexza
orexza
3 years ago

Having worked with playframework for so long, this really solidifies the ideas from Play! awesome.

Pablito
Pablito
3 years ago

Hello,

I see the example with serving static files. However I don’t know how to configure my app to include this rule. I got something like this. How can I add resources to this router?

@Bean
RouterFunction router(final AppHandler handler) {
return route(GET(“/index”), handler::renderIndex);
}

Grzegorz Piwowarek
Grzegorz Piwowarek
3 years ago
Reply to  Pablito

It’s not 100% clear what do you want to achieve. Can you explain what do you want the final result to be like?

Pablito
Pablito
3 years ago

Ok my point is to obtain static resource from standard Spring Boot path which would be classpath/resources/static. To do that I got something like this:

return resources(“/**”, new ClassPathResource(“/resources/static/”))
.andOther(route(GET(“/index”), handler::renderIndex));

However I still got 404 after trying to access static css file.

Pablito
Pablito
3 years ago
Reply to  Pablito

Ok I found it myself it need to be:
resources(“/**”, new ClassPathResource(“/static/”)) because /resources is included in classpath.

Anyways great article I was having troubles for about 10 hours to load static content using RouterFunction. This article is the only source I found. Thank you!

Mohamed Hanafy
Mohamed Hanafy
3 years ago

How can I allow CORS in router functions ?

Grzegorz Piwowarek
Grzegorz Piwowarek
3 years ago
Reply to  Mohamed Hanafy

As I can see, there is no easy way of doing this (or at least it is not documented properly). In that case you need to simply set all headers manually in the .filter((request, next) -> {…} call

Baoqiang
3 years ago
Reply to  Mohamed Hanafy

I thinks there are two things you need to do to handle CORS with router functions.

1. add ‘Access-Control-Allow-Origin’ header to the response: this can be done by prepending a filter with the help of WebHttpHandlerBuilder
2. handle ‘OPTIONS’ request: add a router mapping to handle all valid OPTIONS request

Comments are closed on this article!