The new Certification Class of Learn Spring Security is out:

>> CHECK OUT THE COURSE

1. Introduction

One of the main new features of Spring 5 will be a new Functional Web Framework built using reactive principles.

In this article, we’ll have a look on how it looks like in practice. Keep in mind that the provided code snippets might get outdated quickly because of the constant API changes.

2. Maven Dependency

Let’s start by defining the Maven dependencies that we’re going to need:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.BUILD-SNAPSHOT</version>
    <relativePath/> 
</parent>

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

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

As we are using a snapshot version of Spring Boot, remember to add Spring snapshot repository:

<repositories>
    <repository> 
        <id>repository.spring.snapshot</id> 
        <name>Spring Snapshot Repository</name> 
        <url>http://repo.spring.io/snapshot</url> 
    </repository>
</repositories>

3. Functional Web Framework

Before we start to look at what the new framework provides, it’s a good idea to read this article to brush up on the basics of the Reactive paradigm.

The framework introduces two fundamental components: HandlerFunction and RouterFunction, both located in the org.springframework.web.reactive.function.server package.

3.1. HandlerFunction

The HandlerFunction represents a function that handles incoming requests and generates responses:

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

This interface is primarily a Function<Request, Response<T>>, which is very much like a servlet. Compared to a standard servlet Servlet.service(ServletRequest req, ServletResponse res), a side-effect free HandlerFunction is naturally 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 RequestPredicateWhen 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.

3.3. A Quick Example

Here’s a simple example that serves requests to the root path “/” and returns a response with status 200:

RouterFunction<ServerResponse> routingFunction() {
    return route(path("/"), req -> ok().build());
}

The path(“/”) in the example above is a RequestPredicate. Besides matching a path, there’s a bunch of other commonly used predicates available in RequestPredicates, including HTTP methods, content type matching, etc.

3.4. Running on a Server

To run the functions in a server, we can wrap the RouterFunction in an HttpHandler for serving requests. The HttpHandler a reactive abstraction introduced in Spring 5.0 M1. With this abstraction, we can run our code in reactive runtimes such as Reactor Netty and Servlet 3.1+.

With the following code, we can run the functions in an embedded Tomcat:

HttpHandler httpHandler = RouterFunctions.toHttpHandler(routingFunction());

Tomcat tomcat = new Tomcat();
Context rootContext = tomcat.addContext("", System.getProperty("java.io.tmpdir"));
ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
rootContext.addServletMappingDecoded("/", "httpHandlerServlet");
TomcatWebServer server = new TomcatWebServer(tomcat);
server.start();

We can also deploy the functions in a standalone Servlet container that supports Servlet 3.1+, such as Tomcat 9.

Similar to the code above, we can wrap the RouterFunction in a servlet that extends ServletHttpHandlerAdapter, say com.baeldung.functional.RootServlet:

public class RootServlet extends ServletHttpHandlerAdapter {

    public RootServlet() {
        this(WebHttpHandlerBuilder
          .webHandler(toHttpHandler(routingFunction()))
          .filter(new IndexRewriteFilter())
          .build());
    }

    RootServlet(HttpHandler httpHandler) {
        super(httpHandler);
    }

    //...

}

Then register the Servlet as a bean:

@Bean
public ServletRegistrationBean servletRegistrationBean() throws Exception {
    HttpHandler httpHandler = WebHttpHandlerBuilder
      .webHandler(toHttpHandler(routingFunction()))
      .filter(new IndexRewriteFilter())
      .build();
    ServletRegistrationBean registrationBean
      = new ServletRegistrationBean<>(new RootServlet(httpHandler), "/");
    registrationBean.setLoadOnStartup(1);
    registrationBean.setAsyncSupported(true);
    return registrationBean;
}

Or if you prefer a plain web.xml:

<servlet>
    <servlet-name>functional</servlet-name>
    <servlet-class>com.baeldung.functional.RootServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>
<servlet-mapping>
    <servlet-name>functional</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

As we are using spring-boot-starter, after excluding the embedded tomcat: spring-boot-starter-tomcat included by spring-boot-starter-web; and adding a provided dependency javax.servlet.javax.servlet-api, we are good to go.

4. Testing

A new way of testing for the functional web framework is also introduced in Spring 5. WebTestClient serves as the basis of spring-webflux integration testing support.

With the help of bindToRouterFunction() provided by WebTestClient, we can test the functions without starting an actual server:

WebTestClient client = WebTestClient
  .bindToRouterFunction(routingFunction())
  .build();
client.get().uri("/").exchange().expectStatus().isOk();

If we already have a server running, use bindToServer() to test via socket:

WebTestClient client = WebTestClient
  .bindToServer()
  .baseUrl("http://localhost:8080")
  .build();
client.get().uri("/").exchange().expectStatus().isOk();

5. Conventional Web Request Patterns

Now that we have a basic understanding of the framework’s key components let’s see how they would fit in conventional web request patterns.

5.1. A Simple GET API

A simple HTTP GET API that returns “hello world”:

RouterFunction router = route(GET("/test"), 
  request -> ok().body(fromObject("hello world")));

@Test
public void givenRouter_whenGetTest_thenGotHelloWorld() throws Exception {
    client.get().uri("/test").exchange()
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("hello world");
}

5.2. POST a Form

Posting a login form:

RouterFunction router = route(POST("/login"), req -> req
  .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()))
@Test
public void givenLoginForm_whenPostValidToken_thenSuccess() throws Exception {
    MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(2);
    formData.add("user", "baeldung");
    formData.add("token", "you_know_what_to_do");

    client.post().uri("/login")
      .contentType(MediaType.APPLICATION_FORM_URLENCODED)
      .exchange(BodyInserters.fromFormData(formData))
      .expectStatus().isOk()
      .expectBody(String.class)
      .isEqualTo("welcome back!");
}

Then a form with multipart data:

Mono<ServerResponse> handleUpload(ServerRequest request) {
    return request
      .body(toDataBuffers())
      .collectList()
      .flatMap(dataBuffers -> ok()
      .body(fromObject(extractData(dataBuffers).toString())));
}

private AtomicLong extractData(List<DataBuffer> dataBuffers) {
    AtomicLong atomicLong = new AtomicLong(0);
    dataBuffers.forEach(d -> atomicLong.addAndGet(d
     .asByteBuffer()
     .array().length));
     return atomicLong;
}
@Test
public void givenUploadForm_whenRequestWithMultipartData_thenSuccess()
  throws Exception {

    Resource resource = new ClassPathResource("/baeldung-weekly.png");
    client.post().uri("/upload").contentType(MediaType.MULTIPART_FORM_DATA)
      .exchange(fromResource(resource))
      .expectStatus().isOk()
      .expectBody(String.class)
      .isEqualTo(String.valueOf(resource.contentLength()));
}

As you may notice, the multipart processing is blocking. For now, a non-blocking multipart implementation is still under investigation by the Spring team. You can track this issue for further updates.

5.3. RESTful API

We can also manipulate Resources in a RESTful API:

List<Actor> actors = new CopyOnWriteArrayList<>(
  Arrays.asList(BRAD_PITT, TOM_HANKS));

RouterFunction router = nest(path("/actor"),
  route(GET("/"), request -> 
    ok().body(Flux.fromIterable(actors), Actor.class))

  .andRoute(POST("/"), request -> 
    request.bodyToMono(Actor.class).doOnNext(actors::add)

  .then(ok().build())));

@Test
public void givenActors_whenAddActor_thenAdded() throws Exception {
    client.get().uri("/actor")
      .exchange()
      .expectStatus().isOk()
      .expectBody(Actor.class).list().hasSize(2);

    client.post().uri("/actor")
      .exchange(fromObject(new Actor("Clint", "Eastwood")))
      .expectStatus().isOk();

    client.get().uri("/actor")
      .exchange()
      .expectStatus().isOk()
      .expectBody(Actor.class).list().hasSize(3);
}

As mentioned previously, RouterFunctions and RouterFunction gives us options on chaining and nesting route functions.

In this example, we nested two router functions to separately handle GET and POST request under the path “/actor”.

5.4. Serve Static Resources

RouterFunctions also provides a shortcut to serve static files:

RouterFunction router = resources(
  "/files/**", new ClassPathResource("files/"));

@Test
public void givenResources_whenAccess_thenGotContentHello() throws Exception {
    this.client.get().uri("/files/hello.txt")
      .exchange()
      .expectStatus().isOk()
      .expectBody(String.class)
      .isEqualTo("hello");
}

5.5. Filters

RouterFunction allows filtering handler functions:

router.filter((request, next) -> {
    System.out.println("handling: " + request.path());
    return next.handle(request);
});

However, the filter above only applies to all handler functions routed by the router. When an URL is not explicitly handled, requests to such URLs will not go through the filter. Say we want to add URL rewriting features in such cases, router’s filter would do no help.

Currently, if we want to rewrite URLs in filters, we have to do it in a WebFilter, instead of the router’s filter:

WebHandler webHandler = toHttpHandler(routingFunction());
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(webHandler)
  .filter(((serverWebExchange, webFilterChain) -> {

      ServerHttpRequest request = serverWebExchange.getRequest();
      if (request.getURI().getPath().equals("/")) {

          return webFilterChain.filter(
            serverWebExchange.mutate().request(builder -> 
                builder
                  .method(request.getMethod())
                  .contextPath(request.getContextPath())
                  .path("/test"))
                  .build());

      } else {
          return webFilterChain.filter(serverWebExchange);
      }
  }));

@Test
public void givenIndexFilter_whenRequestRoot_thenRewrittenToTest()
  throws Exception {
    this.client.get().uri("/").exchange()
      .expectStatus().isOk()
      .expectBody(String.class)
      .isEqualTo("hello world");
}

6. Summary

In this article, we introduced the new functional web framework in Spring 5, with detailed examples showing how the framework works in typical scenarios

Note that as of Spring 5.0.0.M5, @RequestMapping and RouterFunction cannot be mixed in the same application yet.

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.

Go deeper into Spring Security with the course:

>> LEARN SPRING SECURITY

Sort by:   newest | oldest | most voted
James Parsons
Guest

So are we saying goodbye to the traditional Spring MVC?

Grzegorz Piwowarek
Guest

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

making
Guest

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

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

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

Pablito
Guest

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

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

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

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

How can I allow CORS in router functions ?

Grzegorz Piwowarek
Guest

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

wpDiscuz