Generic Top

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE COURSE

1. Introduction

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

In this tutorial, we’ll learn how to work with this framework in practice.

We’ll base this off of our existing tutorial Guide to Spring 5 WebFlux.  In that guide, we created a small reactive REST application using Annotation-based components. Here, we’ll use the functional framework instead.

2. Maven Dependency

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

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

3. Functional Web Framework

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.

Also, the functional web framework is built on the same reactive stack on which annotation-based reactive framework was built upon.

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 Servlet.service(ServletRequest req, ServletResponse res), HandlerFunction returns the response instead of taking it as a parameter which makes it side-effect free and easier to test and reuse.

3.2. RouterFunction

RouterFunction serves as an alternative to the @RequestMapping annotation. It’s used for routing incoming requests to handler functions:

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

Typically, we can import RouterFunctions.route(), a helper function 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)

By returning a RouterFunction, route() can be chained and nested to build powerful and complex routing schemes.

4. Reactive REST Application Using Functional Web

In our guide to Spring WebFlux tutorial, we create a small EmployeeManagement REST application using annotated @RestController and WebClient. 

Now, let’s build the same application using Router and Handler functions.

To begin with, we’ll create routes using RouterFunction to publish and consume our reactive streams of Employee. Routes are registered as Spring beans and can be created inside any class that will be used as Spring configuration.

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));
}

To break it down, the first argument defines an HTTP GET request that’ll invoke the handler. I.e. return matched Employee from employeeRepository only if the path is /employee/{id} and the request is of type GET.

4.2. Collection Resource

Next, for publishing 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 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 combine our 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 have used RouterFunction.and() to combine our routes.

Finally, we have created all the REST APIs using Router and Handler that we needed for our EmployeeManagement application.  To run our application we can either use different routes or a single composed one that we created above.

6. Testing Routes

We can use WebTestClient to test our routes. 

To test our routes with WebTestClient we need to bind our routes using bindToRouterFunction and build our test client instance.

Let’s test our getEmployeeByIdRoute:

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

    Employee expected = 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(expected);
}

and similarly getAllEmployeesRoute:

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

    List<Employee> employeeList = new ArrayList<>();

    Employee employee1 = new Employee("1", "Employee 1");
    Employee employee2 = new Employee("2", "Employee 2");

    employeeList.add(employee1);
    employeeList.add(employee2);

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

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

We can also test our updateEmployeeRoute by asserting that updated 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, we looked into its two core functions Router and Handler and we learned how to create various routes to handle the request and send the response.

Also, we recreated our EmployeeManagement application introduced in Guide to Spring 5 WebFlux with our new framework.

Laying its foundation on Reactor, the reactive framework would fully shine with reactive access to data stores. Unfortunately, most data stores do not provide such reactive access yet, except for a few NoSQL databases such as MongoDB.

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

Generic bottom

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE LESSONS

newest oldest most voted
Notify of
James Parsons
Guest
James Parsons

So are we saying goodbye to the traditional Spring MVC?

Grzegorz Piwowarek
Guest
Grzegorz Piwowarek

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
Guest
making

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
Guest
Grzegorz Piwowarek

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
Guest
orexza

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

Pablito
Guest
Pablito

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
Guest
Grzegorz Piwowarek

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
Guest
Pablito

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
Guest
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
Guest
Mohamed Hanafy

How can I allow CORS in router functions ?

Grzegorz Piwowarek
Guest
Grzegorz Piwowarek

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
Guest

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