Starter for building WebFlux applications using Spring Framework's Reactive Web support
—
Spring WebFlux functional routing provides a modern, functional approach to web request handling using router functions and handler functions. This programming model offers composable, lightweight APIs with explicit request routing and functional composition.
@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
Mono<HandlerFunction<T>> route(ServerRequest request);
default RouterFunction<T> and(RouterFunction<T> other);
default RouterFunction<T> andRoute(RequestPredicate predicate, HandlerFunction<T> handlerFunction);
default <S extends ServerResponse> RouterFunction<S> andNest(RequestPredicate predicate, RouterFunction<S> routerFunction);
default RouterFunction<T> andOther(RouterFunction<?> other);
default RouterFunction<T> filter(HandlerFilterFunction<T, T> filterFunction);
default <S extends ServerResponse> RouterFunction<S> before(Function<ServerRequest, ServerRequest> requestProcessor);
default RouterFunction<T> after(BiFunction<ServerRequest, ServerResponse, ServerResponse> responseProcessor);
default RouterFunction<T> onError(Class<? extends Throwable> exceptionType, BiFunction<Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
default RouterFunction<T> onError(Predicate<? super Throwable> predicate, BiFunction<Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
}@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
Mono<T> handle(ServerRequest request);
}@FunctionalInterface
public interface RequestPredicate {
boolean test(ServerRequest request);
default RequestPredicate and(RequestPredicate other);
default RequestPredicate negate();
default RequestPredicate or(RequestPredicate other);
}public class RouterFunctions {
public static <T extends ServerResponse> RouterFunction<T> route(
RequestPredicate predicate,
HandlerFunction<T> handlerFunction);
public static <T extends ServerResponse> RouterFunction<T> nest(
RequestPredicate predicate,
RouterFunction<T> routerFunction);
public static RouterFunction<ServerResponse> resources(String pattern, Resource location);
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction);
public static Builder route();
public static HttpHandler toHttpHandler(RouterFunction<?> routerFunction);
public static HttpHandler toHttpHandler(RouterFunction<?> routerFunction, HandlerStrategies strategies);
public static WebHandler toWebHandler(RouterFunction<?> routerFunction);
public static WebHandler toWebHandler(RouterFunction<?> routerFunction, HandlerStrategies strategies);
}public static final class Builder {
public Builder GET(String pattern, HandlerFunction<ServerResponse> handlerFunction);
public Builder GET(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
public Builder HEAD(String pattern, HandlerFunction<ServerResponse> handlerFunction);
public Builder HEAD(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
public Builder POST(String pattern, HandlerFunction<ServerResponse> handlerFunction);
public Builder POST(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
public Builder PUT(String pattern, HandlerFunction<ServerResponse> handlerFunction);
public Builder PUT(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
public Builder PATCH(String pattern, HandlerFunction<ServerResponse> handlerFunction);
public Builder PATCH(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
public Builder DELETE(String pattern, HandlerFunction<ServerResponse> handlerFunction);
public Builder DELETE(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
public Builder OPTIONS(String pattern, HandlerFunction<ServerResponse> handlerFunction);
public Builder OPTIONS(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
public Builder route(RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
public Builder add(RouterFunction<ServerResponse> routerFunction);
public Builder resources(String pattern, Resource location);
public Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction);
public Builder nest(RequestPredicate predicate, Supplier<RouterFunction<ServerResponse>> routerFunctionSupplier);
public Builder nest(RequestPredicate predicate, RouterFunction<ServerResponse> routerFunction);
public Builder path(String pattern, Supplier<RouterFunction<ServerResponse>> routerFunctionSupplier);
public Builder path(String pattern, RouterFunction<ServerResponse> routerFunction);
public Builder filter(HandlerFilterFunction<ServerResponse, ServerResponse> filterFunction);
public Builder before(Function<ServerRequest, ServerRequest> requestProcessor);
public Builder after(BiFunction<ServerRequest, ServerResponse, ServerResponse> responseProcessor);
public Builder onError(Class<? extends Throwable> exceptionType, BiFunction<Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
public Builder onError(Predicate<? super Throwable> predicate, BiFunction<Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
public RouterFunction<ServerResponse> build();
}public class RequestPredicates {
public static RequestPredicate GET(String pattern);
public static RequestPredicate HEAD(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 OPTIONS(String pattern);
public static RequestPredicate method(HttpMethod httpMethod);
public static RequestPredicate methods(HttpMethod... httpMethods);
public static RequestPredicate all();
public static RequestPredicate path(String pattern);
public static RequestPredicate pathExtension(String extension);
public static RequestPredicate pathExtension(Predicate<String> extensionPredicate);
public static RequestPredicate queryParam(String name, String value);
public static RequestPredicate queryParam(String name, Predicate<String> predicate);
public static RequestPredicate param(String name, String value);
public static RequestPredicate param(String name, Predicate<String> predicate);
public static RequestPredicate header(String name, String value);
public static RequestPredicate header(String name, Predicate<String> predicate);
public static RequestPredicate headers(Predicate<ServerRequest.Headers> headersPredicate);
public static RequestPredicate contentType(MediaType... mediaTypes);
public static RequestPredicate accept(MediaType... mediaTypes);
}public interface ServerRequest {
HttpMethod method();
String methodName();
URI uri();
UriBuilder uriBuilder();
String path();
MultiValueMap<String, String> queryParams();
Headers headers();
MultiValueMap<String, HttpCookie> cookies();
Optional<InetSocketAddress> remoteAddress();
List<HttpMessageReader<?>> messageReaders();
Optional<Object> attribute(String name);
Map<String, Object> attributes();
Optional<String> pathVariable(String name);
Map<String, String> pathVariables();
Flux<DataBuffer> body();
<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);
Mono<MultiValueMap<String, String>> formData();
Mono<MultiValueMap<String, Part>> multipartData();
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 path(String path);
Builder contextPath(String contextPath);
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);
ServerRequest build();
}
}public interface ServerResponse {
HttpStatus statusCode();
int rawStatusCode();
HttpHeaders headers();
MultiValueMap<String, ResponseCookie> cookies();
Mono<Void> writeTo(ServerWebExchange exchange, Context context);
static BodyBuilder status(HttpStatus status);
static BodyBuilder status(int status);
static BodyBuilder ok();
static BodyBuilder created(URI location);
static BodyBuilder accepted();
static BodyBuilder noContent();
static BodyBuilder seeOther(URI location);
static BodyBuilder temporaryRedirect(URI location);
static BodyBuilder permanentRedirect(URI location);
static BodyBuilder badRequest();
static BodyBuilder notFound();
static BodyBuilder unprocessableEntity();
interface BodyBuilder {
BodyBuilder header(String headerName, String... headerValues);
BodyBuilder headers(Consumer<HttpHeaders> headersConsumer);
BodyBuilder cookie(ResponseCookie cookie);
BodyBuilder cookie(String name, String value);
BodyBuilder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
BodyBuilder allow(HttpMethod... allowedMethods);
BodyBuilder allow(Set<HttpMethod> allowedMethods);
BodyBuilder eTag(String etag);
BodyBuilder lastModified(ZonedDateTime lastModified);
BodyBuilder lastModified(Instant lastModified);
BodyBuilder location(URI location);
BodyBuilder cacheControl(CacheControl cacheControl);
BodyBuilder varyBy(String... requestHeaders);
Mono<ServerResponse> build();
Mono<ServerResponse> build(Publisher<Void> voidPublisher);
<T> Mono<ServerResponse> body(T body, Class<T> bodyType);
<T> Mono<ServerResponse> body(T body, ParameterizedTypeReference<T> bodyType);
<T> Mono<ServerResponse> body(Publisher<T> publisher, Class<T> elementClass);
<T> Mono<ServerResponse> body(Publisher<T> publisher, ParameterizedTypeReference<T> elementType);
Mono<ServerResponse> body(Object body, Class<?> bodyType);
Mono<ServerResponse> render(String name, Object... modelAttributes);
Mono<ServerResponse> render(String name, Map<String, ?> model);
}
}@Configuration
public class RouterConfiguration {
@Bean
public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
return RouterFunctions.route()
.GET("/api/users", handler::listUsers)
.GET("/api/users/{id}", handler::getUser)
.POST("/api/users", handler::createUser)
.PUT("/api/users/{id}", handler::updateUser)
.DELETE("/api/users/{id}", handler::deleteUser)
.build();
}
}@Component
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
public Mono<ServerResponse> listUsers(ServerRequest request) {
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userService.findAll(page, size), User.class);
}
public Mono<ServerResponse> getUser(ServerRequest request) {
String id = request.pathVariable("id");
return userService.findById(id)
.flatMap(user -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(User.class)
.flatMap(userService::save)
.flatMap(user -> ServerResponse.created(URI.create("/api/users/" + user.getId()))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user));
}
public Mono<ServerResponse> updateUser(ServerRequest request) {
String id = request.pathVariable("id");
return request.bodyToMono(User.class)
.flatMap(user -> userService.update(id, user))
.flatMap(user -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
String id = request.pathVariable("id");
return userService.deleteById(id)
.flatMap(deleted -> deleted ?
ServerResponse.noContent().build() :
ServerResponse.notFound().build());
}
}@Configuration
public class AdvancedRouterConfiguration {
@Bean
public RouterFunction<ServerResponse> apiRoutes(UserHandler userHandler, OrderHandler orderHandler) {
return RouterFunctions.route()
.path("/api", builder -> builder
.before(this::logRequest)
.after(this::addSecurityHeaders)
.filter(this::authenticate)
.nest(path("/users"), userRoutes(userHandler))
.nest(path("/orders"), orderRoutes(orderHandler))
)
.onError(ValidationException.class, this::handleValidationError)
.onError(ResourceNotFoundException.class, this::handleNotFound)
.build();
}
private RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
return RouterFunctions.route()
.GET("", handler::listUsers)
.GET("/{id}", handler::getUser)
.POST("", handler::createUser)
.PUT("/{id}", handler::updateUser)
.DELETE("/{id}", handler::deleteUser)
.build();
}
private RouterFunction<ServerResponse> orderRoutes(OrderHandler handler) {
return RouterFunctions.route()
.GET("", handler::listOrders)
.GET("/{id}", handler::getOrder)
.POST("", handler::createOrder)
.build();
}
private ServerRequest logRequest(ServerRequest request) {
log.info("Processing request: {} {}", request.method(), request.uri());
return request;
}
private ServerResponse addSecurityHeaders(ServerRequest request, ServerResponse response) {
return ServerResponse.from(response)
.header("X-Content-Type-Options", "nosniff")
.header("X-Frame-Options", "DENY")
.build()
.block();
}
private Mono<ServerResponse> authenticate(ServerRequest request, HandlerFunction<ServerResponse> next) {
return extractToken(request)
.flatMap(this::validateToken)
.flatMap(principal -> next.handle(request.mutate()
.attribute("principal", principal)
.build()))
.switchIfEmpty(ServerResponse.status(HttpStatus.UNAUTHORIZED).build());
}
private Mono<ServerResponse> handleValidationError(Throwable ex, ServerRequest request) {
return ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("error", "VALIDATION_ERROR", "message", ex.getMessage()));
}
private Mono<ServerResponse> handleNotFound(Throwable ex, ServerRequest request) {
return ServerResponse.notFound().build();
}
}@Component
public class ContentHandler {
public Mono<ServerResponse> getData(ServerRequest request) {
List<MediaType> acceptableTypes = request.headers().accept();
if (acceptableTypes.contains(MediaType.APPLICATION_JSON)) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(dataService.getJsonData(), JsonData.class);
} else if (acceptableTypes.contains(MediaType.APPLICATION_XML)) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_XML)
.body(dataService.getXmlData(), XmlData.class);
} else {
return ServerResponse.status(HttpStatus.NOT_ACCEPTABLE).build();
}
}
public Mono<ServerResponse> streamData(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(Flux.interval(Duration.ofSeconds(1))
.map(i -> "data: Event " + i + "\n\n"), String.class);
}
}@Component
public class FileHandler {
public Mono<ServerResponse> uploadFile(ServerRequest request) {
return request.multipartData()
.map(parts -> parts.get("file"))
.cast(List<Part>.class)
.flatMap(parts -> {
if (parts.isEmpty()) {
return ServerResponse.badRequest()
.bodyValue("No file provided");
}
Part filePart = parts.get(0);
if (filePart instanceof FilePart) {
FilePart file = (FilePart) filePart;
return file.transferTo(Paths.get("/tmp/" + file.filename()))
.then(ServerResponse.ok().bodyValue("File uploaded: " + file.filename()));
}
return ServerResponse.badRequest().bodyValue("Invalid file");
});
}
public Mono<ServerResponse> downloadFile(ServerRequest request) {
String filename = request.pathVariable("filename");
Path filePath = Paths.get("/files/" + filename);
if (!Files.exists(filePath)) {
return ServerResponse.notFound().build();
}
Resource resource = new FileSystemResource(filePath);
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
.body(BodyInserters.fromResource(resource));
}
}Functional routing excels at composition, allowing you to build complex routing structures from simple, reusable components:
public class ModularRouting {
public static RouterFunction<ServerResponse> publicRoutes() {
return RouterFunctions.route()
.GET("/health", request -> ServerResponse.ok().bodyValue("OK"))
.GET("/info", request -> ServerResponse.ok().bodyValue("App Info"))
.build();
}
public static RouterFunction<ServerResponse> securedRoutes(AuthHandler authHandler) {
return RouterFunctions.route()
.filter(authHandler::authenticate)
.GET("/profile", authHandler::getProfile)
.POST("/logout", authHandler::logout)
.build();
}
@Bean
public RouterFunction<ServerResponse> allRoutes(AuthHandler authHandler) {
return publicRoutes()
.and(securedRoutes(authHandler))
.filter(this::corsFilter)
.filter(this::loggingFilter);
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-webflux