Over a million developers have joined DZone.

New In Spring 5: Functional Web Framework

An introduction to Spring's new functional web framework includes details on handler and router functions.

· Java Zone

Discover how powerful static code analysis and ergonomic design make development not only productive but also an enjoyable experience, brought to you in partnership with JetBrains

As mentioned yesterday in Juergen’s blog post, the second milestone of Spring 5.0 introduced a new functional web framework. In this post, I will give more information about this framework.


We start with some excerpts from our sample application. Below is a reactive repository that exposes Person objects. It is quite similar to a traditional, non-reactive repository, except that it returns Flux<Person> where you would return a List<Person> traditionally, and Mono<Person> where you would return a Person. Mono<Void> is used as a completion signal: to indicate when the save has been completed. For more information on these Reactor types, refer to this blog post.

public interface PersonRepository {
    Mono<Person> getPerson(int id);
    Flux<Person> allPeople();
    Mono<Void> savePerson(Mono<Person> person);

Here is how we expose that repository with the new functional web framework:

RouterFunction<?> route = route(GET("/person/{id}"),
    request -> {
        Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id"))
        return Response.ok().body(fromPublisher(person, Person.class));
        request -> {
            Flux<Person> people = repository.allPeople();
        return Response.ok().body(fromPublisher(people, Person.class));
    request -> {
        Mono<Person> person = request.body(toMono(Person.class));
    return Response.ok().build(repository.savePerson(person));

And here is how we would run it, for instance in Reactor Netty:

HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
ReactorHttpHandlerAdapter adapter =
    new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create("localhost", 8080);

The final thing to do is to give it a try:

$ curl 'http://localhost:8080/person/1'
{"name":"John Doe","age":42}

There is a lot to cover here, so let’s dig in deeper!

Key Components

I will explain the framework by going over its key components: HandlerFunction, RouterFunction, and FilterFunction. These three interfaces, and all other types described in this post, can be found in the org.springframework.web.reactive.function package.


The starting point of this new framework is the HandlerFunction<T>, which is essentially a Function<Request, Response<T>>, where Request and Response are newly defined, immutable interfaces that offer a JDK-8 friendly DSL to the underlying HTTP messages. There is a convenient builder for building Response instances, quite similar to the one found in ResponseEntity. The annotation counterpart to HandlerFunction would be a method with@RequestMapping.

Here is an example of a simple “Hello World” handler function, that returns a response with a 200 status and a body based on a String:

HandlerFunction<String> helloWorld =
    request -> Response.ok().body(fromObject("Hello World"));

As we saw in the example above, handler functions are fully reactive by building on top of Reactor: they accept Flux, Mono, or any other Reactive Streams Publisher as the response type.

It is important to note that HandlerFunction itself is side-effect free, because it returns the response, as opposed to taking it as a parameter (cf. Servlet.service(ServletRequest,ServletResponse), which essentially is a BiConsumer<ServletRequest,ServletResponse>). Side-effect free functions have many benefits: They are easier to test, compose, and optimize.


Incoming requests are routed to handler functions with a RouterFunction<T> (i.e. Function<Request, Optional<HandlerFunction<T>>). A router function evaluates to a handler function if it matches; otherwise it returns an empty result. The RouterFunction has a similar purpose as a @RequestMapping annotation. However, there is an important distinction: With the annotation, your route is limited to what can be expressed through the annotation values, and the processing of those is not trivial to override; with router functions, the processing code is right in front of you: you can override or replace it quite easily.

Here is an example of a router function with an inline handler function. It does look a bit verbose, but don't worry about that: We will find ways to make it shorter below.

RouterFunction<String> helloWorldRoute = 
    request -> {
        if (request.path().equals("/hello-world")) {
            return Optional.of(r -> Response.ok().body(fromObject("Hello World")));
        } else {
            return Optional.empty();

Typically, you do not write complete router functions, but rather (statically) import RouterFunctions.route(), which allows you to create a RouterFunction using a RequestPredicate (i.e. Predicate<Request>) and a HandlerFunction. If the predicate applies, the handler function is returned; otherwise an empty result. Using route, we can rewrite the above to the following:

RouterFunction<String> helloWorldRoute =
    RouterFunctions.route(request -> request.path().equals("/hello-world"),
        request -> Response.ok().body(fromObject("Hello World")));

You can (statically) import RequestPredicates.* to get access to commonly used predicates, such matching based on path, HTTP method, content-type, etc. With it, we can make our helloWorldRoute even simpler:

RouterFunction<String> helloWorldRoute =
        request -> Response.ok().body(fromObject("Hello World")));

Composing Functions

Two router functions can be composed into a new router function that routes to either handler function: if the first function does not match, the second is evaluated. You can compose two router functions by calling RouterFunction.and(), like so:

RouterFunction<?> route =
        request -> Response.ok().body(fromObject("Hello World")))
        request -> Response.ok().body(fromObject("42"))));

The above will respond with “Hello World” if the path matches /hello-world, and “42” if it matches /the-answer. If neither match, an empty Optional is returned. Note that the composed router functions are evaluated in order, so it makes sense to put generic functions before specific ones.

You can also compose request predicates, by calling and or or. These work as expected: For and, the resulting predicate matches if both given predicates match, whereas or matches if either predicate does. For instance:

RouterFunction<?> route =
        request -> Response.ok().body(fromObject("Hello World")))
        request -> Response.ok().body(fromObject("42"))));

In fact, most of the predicates found in RequestPredicates are compositions! For instance, RequestPredicates.GET(String) is a composition of RequestPredicates.method(HttpMethod) and RequestPredicates.path(String). So we can rewrite the above to:

RouterFunction<?> route =
        request -> Response.ok().body(fromObject("Hello World")))
        request -> Response.ok().body(fromObject(42))));

Method References

As an aside: So far, we have written all handler functions as inline lambdas. While this is fine for demos and short examples, it does have the tendency to get “messy,” as you are mixing two concerns: request routing and request handling. So let’s see if we can make things cleaner. First, we create a class that contains the handling code:

class DemoHandler {
    public Response<String> helloWorld(Request request) {
        return Response.ok().body(fromObject("Hello World"));
    public Response<String> theAnswer(Request request) {
        return Response.ok().body(fromObject("42"));

Note that both methods have a signature that is compatible with a handler function. This allows us to use method references:

DemoHandler handler = new DemoHandler(); // or obtain via DI
RouterFunction<?> route =
    route(GET("/hello-world"), handler::helloWorld)
    .and(route(GET("/the-answer"), handler::theAnswer));


Routes mapped by a router function can be filtered by calling RouterFunction.filter(FilterFunction<T, R>), where FilterFunction<T,R> is essentially a BiFunction<Request, HandlerFunction<T>, Response<R>>. The handler function parameter represents the next item in the chain: this is typically a HandlerFunction, but can also be another FilterFunction if multiple filters are applied. Let’s add a logging filter to our route:

RouterFunction<?> route =
    route(GET("/hello-world"), handler::helloWorld)
    .and(route(GET("/the-answer"), handler::theAnswer))
    .filter((request, next) -> {
        System.out.println("Before handler invocation: " + request.path());
        Response<?> response = next.handle(request);
        Object body = response.body();
        System.out.println("After handler invocation: " + body);
    return response;

Note that invoking the next handler is optional. This is useful in security or caching scenarios (eg. invoke nextonly if the user has sufficient rights).

Because route is an unbounded router function, we do know what type of response the next handler is going to return. This is why we end up with a Response<?> in our filter, and with an Object response body. In our handler class, both methods return a Response<String>, so it should be possible to have a String response body. We can accomplish this by using RouterFunction.andSame() instead of and(). This composition method requires the parameter router function to be of the same type. For example, we can make all responses upper case:

RouterFunction<String> route =
  route(GET("/hello-world"), handler::helloWorld)
  .andSame(route(GET("/the-answer"), handler::theAnswer))
  .filter((request, next) -> {
    Response<String> response = next.handle(request);
    String newBody = response.body().toUpperCase();
    return Response.from(response).body(fromObject(newBody));

With annotations, similar functionality can be achieved using @ControllerAdvice and/or a ServletFilter.

Running a Server

All this is well and good, but there is one piece missing: how can we actually run these functions in an HTTP server? The answer, unsurprisingly, comes by calling another function. You can convert a router function into a HttpHandler by using RouterFunctions.toHttpHandler(). The HttpHandler is a reactive abstraction that was introduced in Spring 5.0 M1: it allows you to run on a wide variety of reactive runtimes: Reactor Netty, RxNetty, Servlet 3.1+, and Undertow. In the example, we already showed what running a route in Reactor Netty looks like. For Tomcat, it looks like this:

HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat server = new Tomcat();
Context rootContext = server.addContext("",
Tomcat.addServlet(rootContext, "servlet", servlet);
rootContext.addServletMapping("/", "servlet");

One thing to note is that the above does not rely on a Spring application context. Just like JdbcTemplate and other Spring utility classes, using a application context is optional: you can wire up your handler and router functions in a context, but it is not required.
Also note that you can also convert a router function into a HandlerMapping, so that it can run in a DispatcherHandler (possibly side-by-side with reactive @Controllers).


This ends the introduction to Spring’s new functional style web framework. Let me conclude by giving a short summary:

  • Handler functions handle request by returning a response.
  • Router functions route to handler functions, and can be composed with other router functions.
  • Router functions can be filtered by filter functions.
  • Router functions can be run in a reactive web runtime.

To give you a more complete picture, I have created a simple sample project that uses the functional web framework. You can find the project on GitHub.

Let us know what you think!

Learn more about Kotlin, a new programming language designed to solve problems that software developers face every day brought to you in partnership with JetBrains.

spring 5,java,framework

Published at DZone with permission of Arjen Poutsma. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}