Backend Development 9 min read

Mastering Spring WebFlux Functional Endpoints: HandlerFunction & RouterFunction Explained

This article explains Spring WebFlux's functional programming model, showing how HandlerFunction and RouterFunction work together to process HTTP requests and responses with reactive types, and provides practical code examples for routing, handling, and validating data.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring WebFlux Functional Endpoints: HandlerFunction & RouterFunction Explained

Overview

Spring WebFlux includes WebFlux.Fn, a lightweight functional programming model where functions are used for routing and handling requests. It runs on the same Reactive Core as the annotation‑based model. In WebFlux.Fn, an HTTP request is processed by a HandlerFunction that receives a ServerRequest and returns a delayed ServerResponse (i.e., Mono<ServerResponse> ). The HandlerFunction corresponds to the body of an @RequestMapping method in the annotation model.

A request is routed to a RouterFunction , a function that takes a ServerRequest and returns a delayed HandlerFunction (i.e., Mono<HandlerFunction> ). When the router matches, it yields the handler; otherwise it returns an empty Mono . RouterFunction is analogous to @RequestMapping but can also provide behavior.

RouterFunctions.route() offers a router builder to create routes easily.

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

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

RouterFunction<ServerResponse> 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 Mono<ServerResponse> listPeople(ServerRequest request) {
    // todo business logic
  }
  public Mono<ServerResponse> createPerson(ServerRequest request) {
    // todo business logic
  }
  public Mono<ServerResponse> getPerson(ServerRequest request) {
    // todo business logic
  }
}
</code>

One way to run a RouterFunction is to convert it to an HttpHandler and install it with a built‑in server adapter, e.g., RouterFunctions.toHttpHandler(routerFunction) or RouterFunctions.toHttpHandler(routerFunction, handlerStrategies) . Most applications can run via WebFlux Java configuration.

HandlerFunction

ServerRequest and ServerResponse are immutable interfaces that provide JDK‑8‑friendly access to HTTP requests and responses. Their bodies are represented by reactive streams such as Flux or Mono . The request body can be extracted as:

<code>Mono<String> string = request.bodyToMono(String.class);
</code>

Or as a Flux of objects:

<code>Flux<Person> people = request.bodyToFlux(Person.class);
</code>

Using the generic ServerRequest.body(BodyExtractor) shortcut:

<code>Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
</code>

Form data can be accessed with:

<code>Mono<MultiValueMap<String, String>> map = request.formData();
</code>

Multipart data as a map:

<code>Mono<MultiValueMap<String, Part>> map = request.multipartData();
</code>

Or stream parts one by one:

<code>Flux<Part> parts = request.body(BodyExtractors.toParts());
</code>

ServerResponse is built immutably. Example of a 200 OK JSON response:

<code>Mono<Person> person = Mono.just(new Person("Zhang San", 12));
ServerResponse.ok()
    .contentType(MediaType.APPLICATION_JSON)
    .body(person, Person.class);
</code>

Example of a 201 CREATED response with a Location header and no body:

<code>URI location = ...;
ServerResponse.created(location).build();
</code>

Hints can customize serialization, e.g., specifying a Jackson JSON view:

<code>ServerResponse.ok()
    .hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class)
    .body(...);
</code>

Handler Classes

Handlers can be written as lambdas:

<code>HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().bodyValue("Hello World");
</code>

For larger applications, grouping related functions into a handler class is preferable. Example:

<code>import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

public class PersonHandler {

    private final PersonRepository repository;

    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }

    public Mono<ServerResponse> listPeople(ServerRequest request) {
        Flux<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) {
        Mono<Person> person = request.bodyToMono(Person.class);
        return ok().build(repository.savePerson(person));
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) {
        int personId = Integer.valueOf(request.pathVariable("id"));
        return repository.getPerson(personId)
            .flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}
</code>

Validation

A functional endpoint can apply Spring’s validation tools to the request body. Example with a custom PersonValidator :

<code>public class PersonHandler {

    private final Validator validator = new PersonValidator();

    public Mono<ServerResponse> createPerson(ServerRequest request) {
        Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate);
        return ok().build(repository.savePerson(person));
    }

    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            throw new ServerWebInputException(errors.toString());
        }
    }
}
</code>

Summary:

Using HandlerFunction within router functions.

Examples of ServerRequest and ServerResponse usage.

Javareactive programmingSpring WebFluxhandlerfunctionrouterfunction
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.