CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-starter-webflux

Starter for building WebFlux applications using Spring Framework's Reactive Web support

Pending
Overview
Eval results
Files

functional-routing.mddocs/

Functional Routing

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.

Core Interfaces

RouterFunction

@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);
}

HandlerFunction

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
    Mono<T> handle(ServerRequest request);
}

RequestPredicate

@FunctionalInterface
public interface RequestPredicate {
    boolean test(ServerRequest request);
    
    default RequestPredicate and(RequestPredicate other);
    default RequestPredicate negate();
    default RequestPredicate or(RequestPredicate other);
}

RouterFunctions Utility Class

Route Creation

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);
}

RouterFunctions.Builder

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();
}

RequestPredicates Utility Class

HTTP Method Predicates

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);
}

ServerRequest Interface

Request Information

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();
    }
}

ServerResponse Interface

Response Creation

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);
    }
}

Usage Examples

Basic Router Configuration

@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();
    }
}

Handler Implementation

@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());
    }
}

Advanced Routing with Filters

@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();
    }
}

Content Negotiation

@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);
    }
}

File Handling

@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));
    }
}

Composition and Modularity

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

docs

annotation-controllers.md

configuration.md

error-handling.md

functional-routing.md

index.md

server-configuration.md

testing.md

webclient.md

tile.json