Backend Development 8 min read

Master Spring Web MVC.fn: Functional Routing and Handlers in Spring Boot 2.7

This article explains Spring Web MVC.fn's functional programming model, covering HandlerFunction, immutable request/response contracts, parameter validation, routing predicates, nested routes, filters, and provides complete code examples for building functional endpoints in Spring Boot 2.7.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring Web MVC.fn: Functional Routing and Handlers in Spring Boot 2.7

1. Introduction

Spring Web MVC includes WebMvc.fn , a lightweight functional programming model where functions handle routing and requests while contracts remain immutable. It serves as an alternative to the annotation‑based model but still relies on the same underlying DispatcherServlet for request processing.

2. Core Concepts

2.1 HandlerFunction

In WebMvc.fn , an HTTP request is processed by a HandlerFunction —a function that receives a ServerRequest and returns a ServerResponse . The request and response objects are immutable and provide JDK 8‑friendly access to headers, body, method, and status.

<code>import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;

PersonRepository repository = ...;
PersonHandler handler = new PersonHandler(repository);

RouterFunction&lt;ServerResponse&gt; route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
    .POST("/person", handler::createPerson)
    .build();

public class PersonHandler {
    public ServerResponse listPeople(ServerRequest request) { /* ... */ }
    public ServerResponse createPerson(ServerRequest request) { /* ... */ }
    public ServerResponse getPerson(ServerRequest request) { /* ... */ }
}</code>

2.2 Request and Response APIs

ServerRequest gives access to HTTP method, URI, headers, query parameters, and body via body(Class) . ServerResponse is built immutably using a builder to set status, headers, and body.

<code>String body = request.body(String.class);
MultiValueMap&lt;String, String&gt; params = request.params();

ServerResponse.ok()
    .contentType(MediaType.APPLICATION_JSON)
    .body(new Person());

Mono&lt;Person&gt; person = webClient.get()
    .retrieve()
    .bodyToMono(Person.class);
ServerResponse.ok()
    .contentType(MediaType.APPLICATION_JSON)
    .body(person);
</code>

2.3 Parameter Validation

Handler functions can perform bean validation using Spring’s Validator .

<code>@Component
public class PersonHandler {
    private final Validator validator;
    public PersonHandler(Validator validator) { this.validator = validator; }
    public ServerResponse createPerson(ServerRequest request) {
        Person person = request.body(Person.class);
        validate(person);
        repository.savePerson(person);
        return ok().build();
    }
    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            throw new ServerWebInputException(errors.toString());
        }
    }
}
</code>

3. Routing Functions

3.1 Predicates

RequestPredicates provides common predicate implementations based on path, method, content type, etc.

<code>RouterFunction&lt;ServerResponse&gt; route = RouterFunctions.route()
    .GET("/demo", accept(MediaType.TEXT_PLAIN), req -> ServerResponse.ok().body("test"))
    .build();
</code>

3.2 Router Builders

Use route() for a fluent builder; .add(otherRoute) can combine additional routes.

<code>import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;

PersonRepository repository = ...;
PersonHandler handler = new PersonHandler(repository);
RouterFunction&lt;ServerResponse&gt; otherRoute = ...;

RouterFunction&lt;ServerResponse&gt; route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
    .POST("/person", handler::createPerson)
    .add(otherRoute)
    .build();
</code>

3.3 Nested Routes

<code>RouterFunction&lt;ServerResponse&gt; route = route()
    .path("/person", b -> b
        .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        .GET(accept(APPLICATION_JSON), handler::listPeople)
        .POST(handler::createPerson))
    .build();
</code>

3.4 Route Filters

Filters can be applied globally with before , after , or filter . They affect all routes built by the same builder; nested route filters do not apply to top‑level routes.

<code>RouterFunction&lt;ServerResponse&gt; route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler::listPeople)
            .before(req -> ServerRequest.from(req)
                .header("X-RequestHeader", "Value")
                .build()))
        .POST(handler::createPerson))
    .after((req, res) -> logResponse(res))
    .build();
</code>

4. Complete Example

<code>@Configuration
public class RouterConfig {
    @Bean
    public UserHandler userHandler() {
        return new UserHandler();
    }

    @Bean
    public RouterFunction&lt;ServerResponse&gt; r1(UserHandler userHandler) {
        return route()
                .GET("/r1/{id}", userHandler::queryUser)
                .build();
    }

    private class UserHandler {
        public Mono&lt;ServerResponse&gt; queryUser(ServerRequest request) {
            return ok().bodyValue("查询 - " + request.pathVariable("id"));
        }
    }
}
</code>

The article ends with a brief note that the content is complete and hopes it is helpful.

Spring BootSpring MVCfunctional routingWebMvc.fnhandlerfunction
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.