Backend Development 10 min read

Mastering Spring Web MVC Functional Routing with WebMvc.fn in Spring Boot

This article explains Spring Web MVC's functional programming model (WebMvc.fn), covering immutable ServerRequest/ServerResponse, RouterFunction routing, HandlerFunction handling, validation, nested routes, filters, and provides comprehensive code examples for Spring Boot 2.4.12.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring Web MVC Functional Routing with WebMvc.fn in Spring Boot

Overview

Spring Web MVC includes WebMvc.fn, a lightweight functional programming model where functions are used for routing and handling requests. The request and response objects are immutable and provide JDK‑8‑friendly access. HandlerFunction corresponds to the body of a @RequestMapping method, and RouterFunction routes incoming requests to the appropriate HandlerFunction.

HandlerFunction Objects

ServerRequest and ServerResponse are immutable interfaces offering access to HTTP method, URI, headers, query parameters, and body.

ServerRequest

<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>

ServerResponse

ServerResponse is immutable; use builder methods to set status, headers, and body.

Handler Classes

Handler classes can be defined separately, similar to @RestController, and injected with DAO components for database operations.

<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();
  }
}
</code>
<code>@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

Spring's validation tools can be applied to request bodies.

<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>
<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-validation&lt;/artifactId&gt;
&lt;/dependency&gt;
</code>

RouterFunction

RouterFunction maps requests to HandlerFunction. Use RouterFunctions.route() to build routes fluently.

<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>

Nested Routing

<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();
}
</code>
<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))
          .POST("/save", ph::save))
      .build();
}
</code>

HandlerMapping

Functional routing still uses DispatcherServlet, so corresponding HandlerMapping and Adapter are present:

RouterFunctionMapping : Detects RouterFunction beans, sorts and combines them.

HandlerFunctionAdapter : Simple adapter that lets DispatcherHandler invoke the mapped HandlerFunction.

Filters

Filters can be added with before, after, or filter methods on the router builder. They apply to all routes generated by the builder, but not to top‑level routes when defined inside nested routes.

<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("参数错误");
          } else {
            return next.handle(request);
          }
        })
      .build();
}

public ServerResponse queryPerson(ServerRequest request) throws Exception {
  System.out.println(request.headers().header("x-pack"));
  return ok().body(new Person(Integer.valueOf(request.pathVariable("id")), "中国"));
}
</code>

Swagger

Swagger support is not applicable in this functional routing context.

Spring Bootfunctional routingWebMvc.fnhandlerfunctionrouterfunction
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.