Master Spring Boot Functional Routing with WebMvc.fn: A Complete Guide
This article explains Spring Web MVC's functional programming model (WebMvc.fn), covering immutable request/response handling, RouterFunction routing, HandlerFunction implementation, validation, nested routes, filters, and provides comprehensive code examples for Spring Boot 2.4.12.
Overview
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model where functions are used for routing and handling requests. HandlerFunction processes a ServerRequest and returns a ServerResponse . The model is immutable and runs on the same DispatcherServlet as the annotation‑based model.
Routing is performed by RouterFunction , which maps a request to an optional HandlerFunction . When a router matches, the corresponding handler is invoked; otherwise the result is empty.
<code>@Configuration
public class PersonHandlerConfiguration {
@Bean
public RouterFunction<ServerResponse> person() {
return route().GET("/person", accept(MediaType.APPLICATION_JSON), request -> {
return ServerResponse.status(HttpStatus.OK).body("Hello World");
}).build();
}
}
</code>Exposing a RouterFunction as a @Bean is sufficient. The three parameters of a GET route are the path, a predicate (similar to @RequestMapping attributes), and the HandlerFunction that contains the business logic.
HandlerFunction Objects
ServerRequest provides immutable access to HTTP method, URI, headers, query parameters, and body via dedicated methods.
<code>@Bean
public RouterFunction<ServerResponse> student() {
return route()
.GET("/student/{id}", accept(MediaType.APPLICATION_JSON), request -> {
return ServerResponse.ok().body(
"name = " + request.param("name").get() + ", id = " + request.pathVariable("id"));
})
.POST("/student", accept(MediaType.APPLICATION_JSON), request -> {
return ServerResponse.ok().body(request.body(Student.class));
})
.build();
}
</code>ServerResponse is also immutable; it is built using a fluent API to set status, headers, and body.
Handler Classes can be defined separately, similar to traditional @RestController classes.
<code>@Configuration
public class PersonHandlerConfiguration {
@Resource
private PersonHandler ph;
@Bean
public RouterFunction<ServerResponse> person() {
return route()
.GET("/person/{id}", accept(MediaType.APPLICATION_JSON), ph::queryPerson)
.POST("/person", accept(MediaType.APPLICATION_JSON), ph::save)
.build();
}
}
@Component
public class PersonHandler {
public ServerResponse save(ServerRequest request) throws Exception {
return ok().body(request.body(Person.class));
}
public ServerResponse queryPerson(ServerRequest request) throws Exception {
return ok().body(new Person(Integer.valueOf(request.pathVariable("id")), "中国"));
}
}
</code>Validation can be applied to request bodies using Spring’s validation infrastructure.
<code>@Component
public class PersonHandler {
@Resource
private Validator validator;
public ServerResponse save(ServerRequest request) throws Exception {
Person person = request.body(Person.class);
Errors errors = validate(person);
if (errors == null) {
return ok().body(person);
}
return ok().body(errors.toString());
}
private Errors validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
return errors.hasErrors() ? errors : null;
}
}
</code>Dependency required:
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</code>RouterFunction
Router functions are typically created with RouterFunctions.route() . The builder provides methods such as GET and POST for common mappings, and predicates can be added to impose additional constraints.
<code>@Bean
public RouterFunction<ServerResponse> hello() {
return route().GET("/hello", accept(MediaType.APPLICATION_JSON), request -> {
return ServerResponse.status(HttpStatus.OK).body("Hello World");
}).build();
}
</code>Multiple predicates can be combined with and or or .
Nested Routes
Nested routing can be expressed with path or nest to set a common prefix.
<code>@Bean
public RouterFunction<ServerResponse> nestPerson() {
return route()
.path("/persons", builder -> builder
.GET("/{id}", accept(MediaType.APPLICATION_JSON), ph::queryPerson)
.POST("/save", ph::save))
.build();
}
@Bean
public RouterFunction<ServerResponse> nestPerson2() {
return route()
.path("/persons2", b1 -> b1
.nest(accept(MediaType.APPLICATION_JSON), b2 -> b2
.GET("/{id}", accept(MediaType.APPLICATION_JSON), ph::queryPerson))
.POST("/save", ph::save))
.build();
}
</code>HandlerMapping
Because the functional model still uses DispatcherServlet , it relies on RouterFunctionMapping (which aggregates all RouterFunction beans) and HandlerFunctionAdapter (which invokes the matched HandlerFunction ).
Filters
Filters can be added with before , after , or filter on the router builder. They apply to all routes generated by the builder, but not to routes defined in a separate nested router.
<code>@Bean
public RouterFunction<ServerResponse> nestPerson2() {
return route()
.path("/persons2", b1 -> b1
.nest(accept(MediaType.APPLICATION_JSON), b2 -> b2
.GET("/{id}", accept(MediaType.APPLICATION_JSON), ph::queryPerson)
.before(request -> ServerRequest.from(request).header("x-pack", "123123").build()))
.POST("/save", ph::save))
.after((request, response) -> {
System.out.println("after execution..." + response.statusCode());
return response;
})
.filter((request, next) -> {
if (request.pathVariable("id").equals("100")) {
return ServerResponse.ok().body("参数错误");
}
return next.handle(request);
})
.build();
}
</code>before adds a custom header that can be read later; after runs after the handler; filter can short‑circuit the chain based on request data.
Swagger
Swagger support is not applicable in this functional routing approach, so use it cautiously.
End of tutorial.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.