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