
Overview
In Spring WebFlux, router functions are used to route requests to the corresponding HandlerFunction. Typically, you don’t write router functions yourself, but use a method in the RouterFunctions handler class to create them. RouterFunctions.route() (with no parameters) gives you a fluent constructor to create a router function, while RouterFunctions.route(RequestPredicate, HandlerFunction) gives you a direct way to create a router.
It is generally recommended to use the route() constructor as it provides convenient shortcuts for
typical mapping scenarios without the need for hard-to-detect static imports. For example,
the router function builder provides a GET(String, HandlerFunction) method to create the mapping
GET requests; and POST(String, HandlerFunction) for POST.
In addition to mapping based on the HTTP method, the route builder offers a way to introduce others
predicates when mapping to requests. For every HTTP method, there is an overloaded variant that takes a RequestPredicate as a parameter, although other restrictions can be expressed.
Predicates
You can write your own RequestPredicate, but the RequestPredicates handler class is commonly provided
implementation used based on request path, HTTP method, content type, and so on. The following example uses the requested predicate to create a constraint based on the Accept header:
Java:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
Kotlin:
val route = coRouter {
GET("/hello-world", accept(TEXT_PLAIN)) {
ServerResponse.ok().bodyValueAndAwait("Hello World")
}
}
You can compose multiple request predicates together by using:
- RequestPredicate.and(RequestPredicate) — both must match.
- RequestPredicate.or(RequestPredicate) — either can match.
Many of the predicates RequestPredicates.GET(String) from is RequestPredicates composed of are
composed. For example, RequestPredicates.method(HttpMethod) and RequestPredicates.path(String). The above example also uses two request predicates because the constructor uses RequestPredicates.GET internally and composes them with the accepted predicate.
Routes
Router functions are evaluated in order: if the first route does not match, the second is evaluated,
and so on. Therefore, it makes sense to declare more specific routes before general ones. It is also
important when registering router functions as Spring beans, as described later. Note that this behavior differs from the annotation-based programming model, where the “most specific” controller method is automatically selected.
When using the router function builder, all defined routes are folded into a single RouterFunction
which is returned from the build(). There are also other ways to build more router features
together:
- add(RouterFunction) on the RouterFunctions.route() builder
- RouterFunction.and(RouterFunction)
- RouterFunction.andRoute(RequestPredicate, HandlerFunction) — shortcut for RouterFunction.and() with nested RouterFunctions.route().
The following example shows the composition of four routes:
Java:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) ①
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) ②
.POST("/person", handler::createPerson) ③
.add(otherRoute) ④
.build();
- GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson
- GET /person with an Accept header that matches JSON is routed to PersonHandler.listPeople
- POST /person with no additional predicates is mapped to PersonHandler.createPerson, and
- otherRoute is a router function that is created elsewhere and added to the route built.
Kotlin:
import org.springframework.http.MediaType.APPLICATION_JSON
val repository: PersonRepository = ...
val handler = PersonHandler(repository);
val otherRoute: RouterFunction<ServerResponse> = coRouter { }
val route = coRouter {
GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
GET("/person", accept(APPLICATION_JSON), handler::listPeople)
POST("/person", handler::createPerson)
}.and(otherRoute)
- GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson
- GET /person with an Accept header that matches JSON is routed to PersonHandler.listPeople
- POST /person with no additional predicates is mapped to PersonHandler.createPerson, and
- otherRoute is a router function that is created elsewhere and added to the route built.
Nested Routes
It is common for a group of router functions to have a shared predicate, such as a shared path.
In the example above, the shared predicate would be the path predicate that matches /person, used
in three ways. When using annotations, you would remove this duplication by using type-
level of the @RequestMapping annotation that maps to /person.
In WebFlux.fn, path predicates can be shared via the path method in the router function builder. For example, the last few lines of the above example can be improved as follows using nested routes:
Java:
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder ①
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST(handler::createPerson))
.build();
Note that the second parameter of the path is a consumer that takes the router builder.
Kotlin:
val route = coRouter {
"/person".nest {
GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
GET(accept(APPLICATION_JSON), handler::listPeople)
POST(handler::createPerson)
}
}
Although path-based nesting is the most common, you can nest on any kind of predicate using
the nest method on the builder. The above still contains some duplication in the form of the shared receive header predicate. We can improve further by using the nesting method along with adopting:
Java:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.build();
Kotlin:
val route = coRouter {
"/person".nest {
accept(APPLICATION_JSON).nest {
GET("/{id}", handler::getPerson)
GET(handler::listPeople)
POST(handler::createPerson)
}
}
}
Conclusion:
In conclusion, in this blog, we have learned about Router Functions in Spring WebFlux. I will be covering more topics on Spring Webflux in my future blogs, stay connected. Happy learning 🙂
For more, you can refer to the WebFlux documentation: https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html
For a more technical blog, you can refer to the Knoldus blog: https://blog.knoldus.com/


