Backend Development 8 min read

Spring WebFlux Functional Endpoints: HandlerFunction, ServerRequest & Response

This article explains how Spring WebFlux's functional programming model uses HandlerFunction, ServerRequest, and ServerResponse to build reactive, immutable HTTP endpoints, covering routing with RouterFunction, request body extraction, response creation, validation, and practical code examples for Java developers.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring WebFlux Functional Endpoints: HandlerFunction, ServerRequest & Response

Overview

Spring WebFlux includes WebFlux.Fn, a lightweight functional programming model where functions are used for routing and handling requests, built on the Reactive Core.

In WebFlux.Fn, HTTP requests are handled by HandlerFunction , which takes a ServerRequest and returns a delayed ServerResponse (Mono<ServerResponse>). HandlerFunction corresponds to the body of a @RequestMapping method in the annotation-based model.

Incoming requests are routed to a RouterFunction , a function that accepts a ServerRequest and returns a delayed HandlerFunction (Mono<HandlerFunction>). RouterFunction is analogous to @RequestMapping but also provides behavior.

RouterFunctions.route() creates a router; example:

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

Running a RouterFunction can be done by converting it to an HttpHandler and installing it with a server adapter, e.g., RouterFunctions.toHttpHandler(...).

HandlerFunction, ServerRequest and ServerResponse

ServerRequest provides JDK‑8‑friendly access to HTTP method, URI, headers, query parameters, and body via body methods. The body can be extracted as a Mono or Flux :

<code>Mono<String> string = request.bodyToMono(String.class);
Flux<Person> people = request.bodyToFlux(Person.class);
</code>

Alternatively, use the generic ServerRequest.body(BodyExtractor) shortcuts:

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

Form data and multipart data can be accessed as:

<code>Mono<MultiValueMap<String, String>> map = request.formData();
Mono<MultiValueMap<String, Part>> map = request.multipartData();
Flux<Part> parts = request.body(BodyExtractors.toParts());
</code>

ServerResponse is immutable and built via a builder. Examples:

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

Handler functions can be written as lambdas:

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

For larger applications, group related functions into a handler class:

<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(p -> ok().contentType(APPLICATION_JSON).bodyValue(p))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}
</code>

Validation

A functional endpoint can apply Spring’s validation tools to the request body:

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

End of tutorial.

JavaFunctional ProgrammingSpring WebFluxhandlerfunctionServerResponseServerRequest
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.