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