Spring WebFlux provides a functional programming model for defining routes and handlers. This approach uses RouterFunction to map requests to HandlerFunction instances, with RequestPredicate for matching criteria. It's an alternative to annotation-based controllers that offers more explicit and composable routing.
The RouterFunctions class is the central entry point for creating routes in the functional programming model.
public class RouterFunctions {
// Create a route that matches the given predicate with a handler function
public static <T extends ServerResponse> RouterFunction<T> route(
RequestPredicate predicate,
HandlerFunction<T> handlerFunction) { ... }
// Nest routes under a common predicate
public static <T extends ServerResponse> RouterFunction<T> nest(
RequestPredicate predicate,
RouterFunction<T> routerFunction) { ... }
// Create route for serving static resources
public static RouterFunction<ServerResponse> resources(
String pattern,
Resource location) { ... }
// Create route for serving resources with custom lookup function
public static RouterFunction<ServerResponse> resources(
Function<ServerRequest, Mono<Resource>> lookupFunction) { ... }
// Convert RouterFunction to HttpHandler
public static HttpHandler toHttpHandler(RouterFunction<?> routerFunction) { ... }
public static HttpHandler toHttpHandler(
RouterFunction<?> routerFunction,
HandlerStrategies strategies) { ... }
// Convert RouterFunction to WebHandler
public static WebHandler toWebHandler(RouterFunction<?> routerFunction) { ... }
public static WebHandler toWebHandler(
RouterFunction<?> routerFunction,
HandlerStrategies strategies) { ... }
// Visitor pattern support
interface Visitor {
void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction);
void nested(RequestPredicate predicate, RouterFunction<?> routerFunction);
void resource(Function<ServerRequest, Mono<Resource>> lookupFunction);
void attributes(Map<String, Object> attributes);
void unknown(RouterFunction<?> routerFunction);
}
// Builder for composing routes
interface Builder {
Builder add(RouterFunction<ServerResponse> routerFunction);
Builder GET(String pattern, HandlerFunction<ServerResponse> handlerFunction);
Builder GET(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
Builder POST(String pattern, HandlerFunction<ServerResponse> handlerFunction);
Builder POST(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
Builder PUT(String pattern, HandlerFunction<ServerResponse> handlerFunction);
Builder PUT(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
Builder PATCH(String pattern, HandlerFunction<ServerResponse> handlerFunction);
Builder PATCH(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
Builder DELETE(String pattern, HandlerFunction<ServerResponse> handlerFunction);
Builder DELETE(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
Builder HEAD(String pattern, HandlerFunction<ServerResponse> handlerFunction);
Builder HEAD(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
Builder OPTIONS(String pattern, HandlerFunction<ServerResponse> handlerFunction);
Builder OPTIONS(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
Builder route(RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
Builder resources(String pattern, Resource location);
Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction);
Builder nest(RequestPredicate predicate, Supplier<RouterFunction<ServerResponse>> routerFunctionSupplier);
Builder nest(RequestPredicate predicate, Consumer<Builder> builderConsumer);
Builder path(String pattern, Supplier<RouterFunction<ServerResponse>> routerFunctionSupplier);
Builder path(String pattern, Consumer<Builder> builderConsumer);
Builder filter(HandlerFilterFunction<ServerResponse, ServerResponse> filterFunction);
Builder before(Function<ServerRequest, ServerRequest> requestProcessor);
Builder after(BiFunction<ServerRequest, ServerResponse, ServerResponse> responseProcessor);
Builder onError(Predicate<? super Throwable> predicate,
BiFunction<? super Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
Builder onError(Class<? extends Throwable> exceptionType,
BiFunction<? super Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
Builder withAttribute(String name, Object value);
Builder withAttributes(Consumer<Map<String, Object>> attributesConsumer);
RouterFunction<ServerResponse> build();
}
}Usage:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> routes(UserHandler handler) {
return route(GET("/users/{id}"), handler::getUser)
.andRoute(GET("/users"), handler::listUsers)
.andRoute(POST("/users"), handler::createUser)
.andRoute(PUT("/users/{id}"), handler::updateUser)
.andRoute(DELETE("/users/{id}"), handler::deleteUser);
}
// Using builder API
@Bean
public RouterFunction<ServerResponse> routesWithBuilder(UserHandler handler) {
return RouterFunctions.route()
.GET("/users/{id}", handler::getUser)
.GET("/users", handler::listUsers)
.POST("/users", handler::createUser)
.PUT("/users/{id}", handler::updateUser)
.DELETE("/users/{id}", handler::deleteUser)
.build();
}
// Nested routes
@Bean
public RouterFunction<ServerResponse> nestedRoutes(UserHandler handler, OrderHandler orderHandler) {
return RouterFunctions.route()
.path("/api", builder -> builder
.nest(path("/users"), userBuilder -> userBuilder
.GET("/{id}", handler::getUser)
.GET("", handler::listUsers)
.POST("", handler::createUser))
.nest(path("/orders"), orderBuilder -> orderBuilder
.GET("/{id}", orderHandler::getOrder)
.POST("", orderHandler::createOrder)))
.build();
}
}The RouterFunction interface routes requests to handler functions.
@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
// Route the request, returning a HandlerFunction if matched
Mono<HandlerFunction<T>> route(ServerRequest request);
// Combine with another router function
default RouterFunction<T> and(RouterFunction<T> other) { ... }
// Add a route
default RouterFunction<T> andRoute(RequestPredicate predicate, HandlerFunction<T> handlerFunction) { ... }
// Nest routes under a predicate
default RouterFunction<T> andNest(RequestPredicate predicate, RouterFunction<T> routerFunction) { ... }
// Add other router function
default RouterFunction<T> andOther(RouterFunction<?> other) { ... }
// Apply filter to all handlers
default RouterFunction<T> filter(HandlerFilterFunction<T, T> filterFunction) { ... }
// Add request processing before handler
default RouterFunction<T> before(Function<ServerRequest, ServerRequest> requestProcessor) { ... }
// Add response processing after handler
default RouterFunction<T> after(BiFunction<ServerRequest, T, T> responseProcessor) { ... }
// Accept visitor for traversing routes
default void accept(RouterFunctions.Visitor visitor) { ... }
// Add attributes to route
default RouterFunction<T> withAttribute(String name, Object value) { ... }
default RouterFunction<T> withAttributes(Consumer<Map<String, Object>> attributesConsumer) { ... }
}The HandlerFunction interface handles a server request and returns a server response.
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
// Handle the given request
Mono<T> handle(ServerRequest request);
}Usage:
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
public class UserHandler {
private final UserRepository repository;
public UserHandler(UserRepository repository) {
this.repository = repository;
}
public Mono<ServerResponse> getUser(ServerRequest request) {
String id = request.pathVariable("id");
return repository.findById(id)
.flatMap(user -> ServerResponse.ok().bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> listUsers(ServerRequest request) {
return ServerResponse.ok().body(repository.findAll(), User.class);
}
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(User.class)
.flatMap(repository::save)
.flatMap(user -> ServerResponse.created(
URI.create("/users/" + user.getId()))
.bodyValue(user));
}
public Mono<ServerResponse> updateUser(ServerRequest request) {
String id = request.pathVariable("id");
return request.bodyToMono(User.class)
.flatMap(user -> repository.update(id, user))
.flatMap(user -> ServerResponse.ok().bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
String id = request.pathVariable("id");
return repository.deleteById(id)
.then(ServerResponse.noContent().build());
}
}The RequestPredicates class provides factory methods for creating request predicates.
public class RequestPredicates {
// Match all requests
public static RequestPredicate all() { ... }
// Match HTTP method
public static RequestPredicate method(HttpMethod httpMethod) { ... }
public static RequestPredicate methods(HttpMethod... httpMethods) { ... }
// Match request path
public static RequestPredicate path(String pattern) { ... }
public static RequestPredicate pathExtension(String extension) { ... }
public static RequestPredicate pathExtension(Predicate<String> extensionPredicate) { ... }
// Match query parameter
public static RequestPredicate queryParam(String name, Predicate<String> predicate) { ... }
public static RequestPredicate queryParam(String name, String value) { ... }
// Match path variable
public static RequestPredicate pathParam(String name, Predicate<String> predicate) { ... }
// Match request parameter
public static RequestPredicate param(String name, String value) { ... }
public static RequestPredicate param(String name, Predicate<String> predicate) { ... }
// Match headers
public static RequestPredicate headers(Predicate<ServerRequest.Headers> headersPredicate) { ... }
// Match content type
public static RequestPredicate contentType(MediaType... mediaTypes) { ... }
// Match accept header
public static RequestPredicate accept(MediaType... mediaTypes) { ... }
// Match API version
public static RequestPredicate version(String version) { ... }
// HTTP method shortcuts
public static RequestPredicate GET(String pattern) { ... }
public static RequestPredicate POST(String pattern) { ... }
public static RequestPredicate PUT(String pattern) { ... }
public static RequestPredicate PATCH(String pattern) { ... }
public static RequestPredicate DELETE(String pattern) { ... }
public static RequestPredicate HEAD(String pattern) { ... }
public static RequestPredicate OPTIONS(String pattern) { ... }
}@FunctionalInterface
public interface RequestPredicate {
// Test if this predicate matches the given request
boolean test(ServerRequest request);
// Combine this predicate with another using AND
default RequestPredicate and(RequestPredicate other) { ... }
// Combine this predicate with another using OR
default RequestPredicate or(RequestPredicate other) { ... }
// Negate this predicate
default RequestPredicate negate() { ... }
// Optional: Nest another predicate
default Optional<ServerRequest> nest(ServerRequest request) { ... }
// Optional: Accept visitor for pattern matching
default void accept(RouterFunctions.Visitor visitor) { ... }
}Usage:
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
// Combine predicates
RequestPredicate predicate = GET("/users")
.and(accept(MediaType.APPLICATION_JSON))
.and(queryParam("active", "true"));
// Content type matching
RequestPredicate jsonPredicate = contentType(MediaType.APPLICATION_JSON);
// Header matching
RequestPredicate headerPredicate = headers(headers ->
headers.header("X-Custom-Header").contains("value"));
// Query parameter matching
RequestPredicate queryPredicate = queryParam("sort", sort ->
sort.equals("asc") || sort.equals("desc"));The ServerRequest interface represents a server-side HTTP request.
public interface ServerRequest {
// HTTP method
HttpMethod method();
// Request URI
URI uri();
// Request path
default String path() { ... }
default PathContainer pathContainer() { ... }
// Request headers
Headers headers();
// Request cookies
MultiValueMap<String, HttpCookie> cookies();
// Remote address
Optional<InetSocketAddress> remoteAddress();
// Local address
Optional<InetSocketAddress> localAddress();
// Path variables
Map<String, String> pathVariables();
String pathVariable(String name);
// Query parameters
MultiValueMap<String, String> queryParams();
default Optional<String> queryParam(String name) { ... }
// Request parameters (query + form)
MultiValueMap<String, String> params();
default Optional<String> param(String name) { ... }
// Attributes
Map<String, Object> attributes();
default Optional<Object> attribute(String name) { ... }
// Body extraction
<T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor);
<T> Mono<T> bodyToMono(Class<? extends T> elementClass);
<T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference);
<T> Flux<T> bodyToFlux(Class<? extends T> elementClass);
<T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference);
// Form data
Mono<MultiValueMap<String, String>> formData();
// Multipart data
Mono<MultiValueMap<String, Part>> multipartData();
// Principal
default Mono<? extends Principal> principal() { ... }
// Session
default Mono<WebSession> session() { ... }
// Exchange
default ServerWebExchange exchange() { ... }
// Check if modified since
default Mono<Boolean> checkNotModified(Instant lastModified) { ... }
default Mono<Boolean> checkNotModified(String etag) { ... }
default Mono<Boolean> checkNotModified(Instant lastModified, String etag) { ... }
// Builder for creating ServerRequest
static Builder from(ServerRequest other) { ... }
interface Headers {
List<MediaType> accept();
List<Charset> acceptCharset();
List<Locale.LanguageRange> acceptLanguage();
OptionalLong contentLength();
Optional<MediaType> contentType();
InetSocketAddress host();
List<HttpRange> range();
List<String> header(String headerName);
HttpHeaders asHttpHeaders();
}
interface Builder {
Builder method(HttpMethod method);
Builder uri(URI uri);
Builder header(String headerName, String... headerValues);
Builder headers(Consumer<HttpHeaders> headersConsumer);
Builder cookie(String name, String... values);
Builder cookies(Consumer<MultiValueMap<String, HttpCookie>> cookiesConsumer);
Builder body(Flux<DataBuffer> body);
Builder body(String body);
Builder attribute(String name, Object value);
Builder attributes(Consumer<Map<String, Object>> attributesConsumer);
Mono<ServerRequest> build();
}
}Usage:
public Mono<ServerResponse> handleRequest(ServerRequest request) {
// Extract path variable
String id = request.pathVariable("id");
// Extract query parameter
Optional<String> sort = request.queryParam("sort");
// Extract header
List<String> customHeaders = request.headers().header("X-Custom-Header");
// Extract body
Mono<User> userMono = request.bodyToMono(User.class);
// Extract form data
Mono<MultiValueMap<String, String>> formData = request.formData();
// Check attributes
Optional<Object> attr = request.attribute("customAttribute");
return userMono.flatMap(user -> ServerResponse.ok().bodyValue(user));
}The ServerResponse interface represents a typed server-side HTTP response.
public interface ServerResponse {
// Response status code
HttpStatusCode statusCode();
// Response headers
HttpHeaders headers();
// Response cookies
MultiValueMap<String, ResponseCookie> cookies();
// Write response to exchange
Mono<Void> writeTo(ServerWebExchange exchange, Context context);
// Factory methods for status codes
static BodyBuilder ok() { ... }
static BodyBuilder created(URI location) { ... }
static BodyBuilder accepted() { ... }
static BodyBuilder noContent() { ... }
static BodyBuilder badRequest() { ... }
static BodyBuilder notFound() { ... }
static BodyBuilder unprocessableEntity() { ... }
static BodyBuilder status(HttpStatusCode status) { ... }
static BodyBuilder status(int status) { ... }
// Redirect responses
static BodyBuilder seeOther(URI location) { ... }
static BodyBuilder temporaryRedirect(URI location) { ... }
static BodyBuilder permanentRedirect(URI location) { ... }
// Server-Sent Events
static <T> ServerResponse.SseBuilder sse(Consumer<SseBuilder.SendOperations<T>> consumer) { ... }
static ServerResponse.SseBuilder sse() { ... }
// Builder from another response
static Builder from(ServerResponse other) { ... }
interface HeadersBuilder<B extends HeadersBuilder<B>> {
B header(String headerName, String... headerValues);
B headers(Consumer<HttpHeaders> headersConsumer);
B cookie(ResponseCookie cookie);
B cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
B allow(HttpMethod... allowedMethods);
B allow(Set<HttpMethod> allowedMethods);
B eTag(String etag);
B lastModified(ZonedDateTime lastModified);
B lastModified(Instant lastModified);
B location(URI location);
B cacheControl(CacheControl cacheControl);
B varyBy(String... requestHeaders);
Mono<ServerResponse> build();
Mono<ServerResponse> build(Publisher<Void> voidPublisher);
}
interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
BodyBuilder contentLength(long contentLength);
BodyBuilder contentType(MediaType contentType);
Mono<ServerResponse> bodyValue(Object body);
<T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, Class<T> elementClass);
<T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, ParameterizedTypeReference<T> elementTypeRef);
Mono<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> inserter);
Mono<ServerResponse> render(String name, Object... modelAttributes);
Mono<ServerResponse> render(String name, Map<String, ?> model);
}
interface Context {
List<HttpMessageWriter<?>> messageWriters();
List<ViewResolver> viewResolvers();
}
}Usage:
// OK response with body
public Mono<ServerResponse> getUser(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user);
}
// Created response with location
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(User.class)
.flatMap(repository::save)
.flatMap(user -> ServerResponse.created(URI.create("/users/" + user.getId()))
.bodyValue(user));
}
// Not found response
public Mono<ServerResponse> userNotFound() {
return ServerResponse.notFound().build();
}
// No content response
public Mono<ServerResponse> deleteUser(ServerRequest request) {
return repository.deleteById(request.pathVariable("id"))
.then(ServerResponse.noContent().build());
}
// Response with custom headers
public Mono<ServerResponse> withHeaders(ServerRequest request) {
return ServerResponse.ok()
.header("X-Custom-Header", "value")
.cookie(ResponseCookie.from("sessionId", "abc123").build())
.cacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS))
.bodyValue(data);
}
// Render view with model
public Mono<ServerResponse> renderView(ServerRequest request) {
return ServerResponse.ok()
.render("userView", "user", user, "timestamp", Instant.now());
}The EntityResponse interface represents a server response with a typed entity body.
public interface EntityResponse<T> extends ServerResponse {
// Get the entity
T entity();
// Create entity response
static <T> Builder<T> fromObject(T t) { ... }
static <T> Builder<T> fromPublisher(Publisher<T> publisher, Class<T> entityClass) { ... }
static <T> Builder<T> fromPublisher(Publisher<T> publisher, ParameterizedTypeReference<T> entityType) { ... }
interface Builder<T> {
Builder<T> status(HttpStatusCode status);
Builder<T> status(int status);
Builder<T> cookie(ResponseCookie cookie);
Builder<T> cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
Builder<T> header(String headerName, String... headerValues);
Builder<T> headers(Consumer<HttpHeaders> headersConsumer);
Builder<T> allow(HttpMethod... allowedMethods);
Builder<T> allow(Set<HttpMethod> allowedMethods);
Builder<T> contentLength(long contentLength);
Builder<T> contentType(MediaType contentType);
Builder<T> eTag(String etag);
Builder<T> lastModified(ZonedDateTime lastModified);
Builder<T> lastModified(Instant lastModified);
Builder<T> location(URI location);
Builder<T> cacheControl(CacheControl cacheControl);
Builder<T> varyBy(String... requestHeaders);
Mono<EntityResponse<T>> build();
}
}The RenderingResponse interface represents a server response for rendering a view.
public interface RenderingResponse extends ServerResponse {
// Get view name
String name();
// Get model
Map<String, Object> model();
// Create rendering response
static Builder create(String name) { ... }
interface Builder {
Builder status(HttpStatusCode status);
Builder status(int status);
Builder cookie(ResponseCookie cookie);
Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
Builder header(String headerName, String... headerValues);
Builder headers(Consumer<HttpHeaders> headersConsumer);
Builder modelAttribute(Object attribute);
Builder modelAttribute(String name, Object value);
Builder modelAttributes(Object... attributes);
Builder modelAttributes(Map<String, ?> attributes);
Mono<RenderingResponse> build();
}
}The HandlerFilterFunction interface filters handler functions.
@FunctionalInterface
public interface HandlerFilterFunction<T extends ServerResponse, R extends ServerResponse> {
// Filter the handler function
Mono<R> filter(ServerRequest request, HandlerFunction<T> next);
// Chain filters
default <S extends ServerResponse> HandlerFilterFunction<T, S> andThen(
HandlerFilterFunction<R, S> after) { ... }
}Usage:
// Logging filter
HandlerFilterFunction<ServerResponse, ServerResponse> loggingFilter =
(request, next) -> {
System.out.println("Request: " + request.method() + " " + request.path());
return next.handle(request)
.doOnNext(response -> System.out.println("Response: " + response.statusCode()));
};
// Authentication filter
HandlerFilterFunction<ServerResponse, ServerResponse> authFilter =
(request, next) -> {
Optional<String> authHeader = request.headers().firstHeader("Authorization");
if (authHeader.isPresent() && isValid(authHeader.get())) {
return next.handle(request);
}
return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
};
// Apply filters to routes
RouterFunction<ServerResponse> routes = RouterFunctions.route()
.GET("/users", handler::listUsers)
.filter(loggingFilter)
.filter(authFilter)
.build();The HandlerStrategies interface provides strategies for handler functions.
public interface HandlerStrategies {
// Get message readers
List<HttpMessageReader<?>> messageReaders();
// Get message writers
List<HttpMessageWriter<?>> messageWriters();
// Get view resolvers
List<ViewResolver> viewResolvers();
// Create with defaults
static HandlerStrategies withDefaults() { ... }
// Create builder
static Builder builder() { ... }
// Create builder from existing
Builder mutate();
interface Builder {
Builder codecs(Consumer<ServerCodecConfigurer> consumer);
Builder defaultCodecs();
Builder viewResolver(ViewResolver viewResolver);
Builder exceptionHandler(WebExceptionHandler exceptionHandler);
Builder webFilter(WebFilter webFilter);
HandlerStrategies build();
}
}public class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
public ResourceHandlerFunction(Function<ServerRequest, Mono<Resource>> lookupFunction) { ... }
@Override
public Mono<ServerResponse> handle(ServerRequest request) { ... }
}public class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resource>> {
public PathResourceLookupFunction(String pattern, Resource location) { ... }
@Override
public Mono<Resource> apply(ServerRequest request) { ... }
}public class PredicateResourceLookupFunction implements Function<ServerRequest, Mono<Resource>> {
public PredicateResourceLookupFunction(RequestPredicate predicate, Resource location) { ... }
@Override
public Mono<Resource> apply(ServerRequest request) { ... }
}public final class RouterFunctionBuilder implements RouterFunctions.Builder {
// All builder methods for composing routes
}The BodyExtractor interface and BodyExtractors utility class provide functionality for extracting content from request bodies.
@FunctionalInterface
public interface BodyExtractor<T, M extends ReactiveHttpInputMessage> {
// Extract from the given input message
T extract(M inputMessage, Context context);
interface Context {
List<HttpMessageReader<?>> messageReaders();
Optional<ServerHttpResponse> serverResponse();
Map<String, Object> hints();
}
}Factory methods for creating body extractors:
public abstract class BodyExtractors {
// Extract to Mono
public static <T> BodyExtractor<Mono<T>, ReactiveHttpInputMessage> toMono(Class<? extends T> elementClass) { ... }
public static <T> BodyExtractor<Mono<T>, ReactiveHttpInputMessage> toMono(ParameterizedTypeReference<T> elementTypeRef) { ... }
// Extract to Flux
public static <T> BodyExtractor<Flux<T>, ReactiveHttpInputMessage> toFlux(Class<? extends T> elementClass) { ... }
public static <T> BodyExtractor<Flux<T>, ReactiveHttpInputMessage> toFlux(ParameterizedTypeReference<T> typeRef) { ... }
// Extract form data
public static BodyExtractor<Mono<MultiValueMap<String, String>>, ReactiveHttpInputMessage> toFormData() { ... }
// Extract multipart data
public static BodyExtractor<Mono<MultiValueMap<String, Part>>, ServerHttpRequest> toMultipartData() { ... }
// Extract parts
public static BodyExtractor<Flux<Part>, ServerHttpRequest> toParts() { ... }
// Extract data buffers
public static BodyExtractor<Flux<DataBuffer>, ReactiveHttpInputMessage> toDataBuffers() { ... }
}Usage Examples:
// Extract body to Mono
public Mono<ServerResponse> handleRequest(ServerRequest request) {
Mono<User> userMono = request.body(BodyExtractors.toMono(User.class));
return userMono.flatMap(user -> ServerResponse.ok().bodyValue(user));
}
// Extract body to Flux
public Mono<ServerResponse> handleStreamRequest(ServerRequest request) {
Flux<Event> events = request.body(BodyExtractors.toFlux(Event.class));
return ServerResponse.ok().body(events, Event.class);
}
// Extract form data
public Mono<ServerResponse> handleFormSubmit(ServerRequest request) {
Mono<MultiValueMap<String, String>> formData = request.body(BodyExtractors.toFormData());
return formData.flatMap(form -> {
String username = form.getFirst("username");
return ServerResponse.ok().bodyValue("Hello, " + username);
});
}
// Extract multipart data
public Mono<ServerResponse> handleFileUpload(ServerRequest request) {
Mono<MultiValueMap<String, Part>> multipartData = request.body(BodyExtractors.toMultipartData());
return multipartData.flatMap(parts -> {
Part filePart = parts.getFirst("file");
// Process file
return ServerResponse.ok().build();
});
}The BodyInserter interface and BodyInserters utility class provide functionality for inserting content into response bodies.
@FunctionalInterface
public interface BodyInserter<T, M extends ReactiveHttpOutputMessage> {
// Insert into the given output message
Mono<Void> insert(M outputMessage, Context context);
interface Context {
List<HttpMessageWriter<?>> messageWriters();
Optional<ServerHttpRequest> serverRequest();
Map<String, Object> hints();
}
}Factory methods for creating body inserters:
public abstract class BodyInserters {
// Empty inserter
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> empty() { ... }
// Insert single value
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromValue(T body) { ... }
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromValue(T body, ParameterizedTypeReference<T> bodyType) { ... }
// Insert from producer (Mono, Flux, or other reactive type)
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromProducer(T producer, Class<?> elementClass) { ... }
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromProducer(T producer, ParameterizedTypeReference<?> elementTypeRef) { ... }
// Insert from Publisher
public static <T, P extends Publisher<T>> BodyInserter<P, ReactiveHttpOutputMessage> fromPublisher(P publisher, Class<T> elementClass) { ... }
public static <T, P extends Publisher<T>> BodyInserter<P, ReactiveHttpOutputMessage> fromPublisher(P publisher, ParameterizedTypeReference<T> elementTypeRef) { ... }
// Insert resource
public static <T extends Resource> BodyInserter<T, ReactiveHttpOutputMessage> fromResource(T resource) { ... }
// Insert Server-Sent Events
public static <T, S extends Publisher<ServerSentEvent<T>>> BodyInserter<S, ServerHttpResponse> fromServerSentEvents(S eventsPublisher) { ... }
// Insert form data
public static BodyInserter<MultiValueMap<String, String>, ClientHttpRequest> fromFormData(MultiValueMap<String, String> formData) { ... }
// Insert multipart data
public static BodyInserter<MultiValueMap<String, ?>, ClientHttpRequest> fromMultipartData(MultiValueMap<String, ?> multipartData) { ... }
// Insert data buffers
public static BodyInserter<Publisher<DataBuffer>, ReactiveHttpOutputMessage> fromDataBuffers(Publisher<DataBuffer> dataBuffers) { ... }
}Usage Examples:
// Insert single value
public Mono<ServerResponse> respondWithUser(User user) {
return ServerResponse.ok()
.body(BodyInserters.fromValue(user));
}
// Insert from Publisher
public Mono<ServerResponse> respondWithStream(Flux<Event> events) {
return ServerResponse.ok()
.body(BodyInserters.fromPublisher(events, Event.class));
}
// Insert resource
public Mono<ServerResponse> serveFile(Resource fileResource) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(BodyInserters.fromResource(fileResource));
}
// Insert Server-Sent Events
public Mono<ServerResponse> streamEvents(ServerRequest request) {
Flux<ServerSentEvent<String>> events = Flux.interval(Duration.ofSeconds(1))
.map(seq -> ServerSentEvent.builder("Event " + seq)
.id(String.valueOf(seq))
.event("message")
.build());
return ServerResponse.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(BodyInserters.fromServerSentEvents(events));
}Note: In most cases, you can use the convenience methods like bodyValue() and body() on ServerResponse instead of using BodyInserters directly. These utility classes are primarily useful for custom body processing scenarios or when you need fine-grained control over the serialization process.