0
# Functional Routing
1
2
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.
3
4
## Core Interfaces
5
6
### RouterFunction
7
8
```java { .api }
9
@FunctionalInterface
10
public interface RouterFunction<T extends ServerResponse> {
11
Mono<HandlerFunction<T>> route(ServerRequest request);
12
13
default RouterFunction<T> and(RouterFunction<T> other);
14
default RouterFunction<T> andRoute(RequestPredicate predicate, HandlerFunction<T> handlerFunction);
15
default <S extends ServerResponse> RouterFunction<S> andNest(RequestPredicate predicate, RouterFunction<S> routerFunction);
16
default RouterFunction<T> andOther(RouterFunction<?> other);
17
default RouterFunction<T> filter(HandlerFilterFunction<T, T> filterFunction);
18
default <S extends ServerResponse> RouterFunction<S> before(Function<ServerRequest, ServerRequest> requestProcessor);
19
default RouterFunction<T> after(BiFunction<ServerRequest, ServerResponse, ServerResponse> responseProcessor);
20
default RouterFunction<T> onError(Class<? extends Throwable> exceptionType, BiFunction<Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
21
default RouterFunction<T> onError(Predicate<? super Throwable> predicate, BiFunction<Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
22
}
23
```
24
25
### HandlerFunction
26
27
```java { .api }
28
@FunctionalInterface
29
public interface HandlerFunction<T extends ServerResponse> {
30
Mono<T> handle(ServerRequest request);
31
}
32
```
33
34
### RequestPredicate
35
36
```java { .api }
37
@FunctionalInterface
38
public interface RequestPredicate {
39
boolean test(ServerRequest request);
40
41
default RequestPredicate and(RequestPredicate other);
42
default RequestPredicate negate();
43
default RequestPredicate or(RequestPredicate other);
44
}
45
```
46
47
## RouterFunctions Utility Class
48
49
### Route Creation
50
51
```java { .api }
52
public class RouterFunctions {
53
54
public static <T extends ServerResponse> RouterFunction<T> route(
55
RequestPredicate predicate,
56
HandlerFunction<T> handlerFunction);
57
58
public static <T extends ServerResponse> RouterFunction<T> nest(
59
RequestPredicate predicate,
60
RouterFunction<T> routerFunction);
61
62
public static RouterFunction<ServerResponse> resources(String pattern, Resource location);
63
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction);
64
65
public static Builder route();
66
67
public static HttpHandler toHttpHandler(RouterFunction<?> routerFunction);
68
public static HttpHandler toHttpHandler(RouterFunction<?> routerFunction, HandlerStrategies strategies);
69
70
public static WebHandler toWebHandler(RouterFunction<?> routerFunction);
71
public static WebHandler toWebHandler(RouterFunction<?> routerFunction, HandlerStrategies strategies);
72
}
73
```
74
75
### RouterFunctions.Builder
76
77
```java { .api }
78
public static final class Builder {
79
80
public Builder GET(String pattern, HandlerFunction<ServerResponse> handlerFunction);
81
public Builder GET(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
82
83
public Builder HEAD(String pattern, HandlerFunction<ServerResponse> handlerFunction);
84
public Builder HEAD(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
85
86
public Builder POST(String pattern, HandlerFunction<ServerResponse> handlerFunction);
87
public Builder POST(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
88
89
public Builder PUT(String pattern, HandlerFunction<ServerResponse> handlerFunction);
90
public Builder PUT(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
91
92
public Builder PATCH(String pattern, HandlerFunction<ServerResponse> handlerFunction);
93
public Builder PATCH(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
94
95
public Builder DELETE(String pattern, HandlerFunction<ServerResponse> handlerFunction);
96
public Builder DELETE(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
97
98
public Builder OPTIONS(String pattern, HandlerFunction<ServerResponse> handlerFunction);
99
public Builder OPTIONS(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
100
101
public Builder route(RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction);
102
public Builder add(RouterFunction<ServerResponse> routerFunction);
103
public Builder resources(String pattern, Resource location);
104
public Builder resources(Function<ServerRequest, Mono<Resource>> lookupFunction);
105
public Builder nest(RequestPredicate predicate, Supplier<RouterFunction<ServerResponse>> routerFunctionSupplier);
106
public Builder nest(RequestPredicate predicate, RouterFunction<ServerResponse> routerFunction);
107
public Builder path(String pattern, Supplier<RouterFunction<ServerResponse>> routerFunctionSupplier);
108
public Builder path(String pattern, RouterFunction<ServerResponse> routerFunction);
109
110
public Builder filter(HandlerFilterFunction<ServerResponse, ServerResponse> filterFunction);
111
public Builder before(Function<ServerRequest, ServerRequest> requestProcessor);
112
public Builder after(BiFunction<ServerRequest, ServerResponse, ServerResponse> responseProcessor);
113
public Builder onError(Class<? extends Throwable> exceptionType, BiFunction<Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
114
public Builder onError(Predicate<? super Throwable> predicate, BiFunction<Throwable, ServerRequest, Mono<ServerResponse>> responseProvider);
115
116
public RouterFunction<ServerResponse> build();
117
}
118
```
119
120
## RequestPredicates Utility Class
121
122
### HTTP Method Predicates
123
124
```java { .api }
125
public class RequestPredicates {
126
127
public static RequestPredicate GET(String pattern);
128
public static RequestPredicate HEAD(String pattern);
129
public static RequestPredicate POST(String pattern);
130
public static RequestPredicate PUT(String pattern);
131
public static RequestPredicate PATCH(String pattern);
132
public static RequestPredicate DELETE(String pattern);
133
public static RequestPredicate OPTIONS(String pattern);
134
135
public static RequestPredicate method(HttpMethod httpMethod);
136
public static RequestPredicate methods(HttpMethod... httpMethods);
137
138
public static RequestPredicate all();
139
140
public static RequestPredicate path(String pattern);
141
public static RequestPredicate pathExtension(String extension);
142
public static RequestPredicate pathExtension(Predicate<String> extensionPredicate);
143
144
public static RequestPredicate queryParam(String name, String value);
145
public static RequestPredicate queryParam(String name, Predicate<String> predicate);
146
147
public static RequestPredicate param(String name, String value);
148
public static RequestPredicate param(String name, Predicate<String> predicate);
149
150
public static RequestPredicate header(String name, String value);
151
public static RequestPredicate header(String name, Predicate<String> predicate);
152
public static RequestPredicate headers(Predicate<ServerRequest.Headers> headersPredicate);
153
154
public static RequestPredicate contentType(MediaType... mediaTypes);
155
public static RequestPredicate accept(MediaType... mediaTypes);
156
}
157
```
158
159
## ServerRequest Interface
160
161
### Request Information
162
163
```java { .api }
164
public interface ServerRequest {
165
166
HttpMethod method();
167
String methodName();
168
URI uri();
169
UriBuilder uriBuilder();
170
String path();
171
MultiValueMap<String, String> queryParams();
172
173
Headers headers();
174
MultiValueMap<String, HttpCookie> cookies();
175
176
Optional<InetSocketAddress> remoteAddress();
177
List<HttpMessageReader<?>> messageReaders();
178
179
Optional<Object> attribute(String name);
180
Map<String, Object> attributes();
181
182
Optional<String> pathVariable(String name);
183
Map<String, String> pathVariables();
184
185
Flux<DataBuffer> body();
186
<T> Mono<T> bodyToMono(Class<? extends T> elementClass);
187
<T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference);
188
<T> Flux<T> bodyToFlux(Class<? extends T> elementClass);
189
<T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference);
190
191
Mono<MultiValueMap<String, String>> formData();
192
Mono<MultiValueMap<String, Part>> multipartData();
193
194
static Builder from(ServerRequest other);
195
196
interface Headers {
197
List<MediaType> accept();
198
List<Charset> acceptCharset();
199
List<Locale.LanguageRange> acceptLanguage();
200
OptionalLong contentLength();
201
Optional<MediaType> contentType();
202
InetSocketAddress host();
203
List<HttpRange> range();
204
205
List<String> header(String headerName);
206
HttpHeaders asHttpHeaders();
207
}
208
209
interface Builder {
210
Builder method(HttpMethod method);
211
Builder uri(URI uri);
212
Builder path(String path);
213
Builder contextPath(String contextPath);
214
Builder header(String headerName, String... headerValues);
215
Builder headers(Consumer<HttpHeaders> headersConsumer);
216
Builder cookie(String name, String... values);
217
Builder cookies(Consumer<MultiValueMap<String, HttpCookie>> cookiesConsumer);
218
Builder body(Flux<DataBuffer> body);
219
Builder body(String body);
220
Builder attribute(String name, Object value);
221
Builder attributes(Consumer<Map<String, Object>> attributesConsumer);
222
223
ServerRequest build();
224
}
225
}
226
```
227
228
## ServerResponse Interface
229
230
### Response Creation
231
232
```java { .api }
233
public interface ServerResponse {
234
235
HttpStatus statusCode();
236
int rawStatusCode();
237
HttpHeaders headers();
238
MultiValueMap<String, ResponseCookie> cookies();
239
240
Mono<Void> writeTo(ServerWebExchange exchange, Context context);
241
242
static BodyBuilder status(HttpStatus status);
243
static BodyBuilder status(int status);
244
static BodyBuilder ok();
245
static BodyBuilder created(URI location);
246
static BodyBuilder accepted();
247
static BodyBuilder noContent();
248
static BodyBuilder seeOther(URI location);
249
static BodyBuilder temporaryRedirect(URI location);
250
static BodyBuilder permanentRedirect(URI location);
251
static BodyBuilder badRequest();
252
static BodyBuilder notFound();
253
static BodyBuilder unprocessableEntity();
254
255
interface BodyBuilder {
256
BodyBuilder header(String headerName, String... headerValues);
257
BodyBuilder headers(Consumer<HttpHeaders> headersConsumer);
258
BodyBuilder cookie(ResponseCookie cookie);
259
BodyBuilder cookie(String name, String value);
260
BodyBuilder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
261
BodyBuilder allow(HttpMethod... allowedMethods);
262
BodyBuilder allow(Set<HttpMethod> allowedMethods);
263
BodyBuilder eTag(String etag);
264
BodyBuilder lastModified(ZonedDateTime lastModified);
265
BodyBuilder lastModified(Instant lastModified);
266
BodyBuilder location(URI location);
267
BodyBuilder cacheControl(CacheControl cacheControl);
268
BodyBuilder varyBy(String... requestHeaders);
269
270
Mono<ServerResponse> build();
271
Mono<ServerResponse> build(Publisher<Void> voidPublisher);
272
<T> Mono<ServerResponse> body(T body, Class<T> bodyType);
273
<T> Mono<ServerResponse> body(T body, ParameterizedTypeReference<T> bodyType);
274
<T> Mono<ServerResponse> body(Publisher<T> publisher, Class<T> elementClass);
275
<T> Mono<ServerResponse> body(Publisher<T> publisher, ParameterizedTypeReference<T> elementType);
276
Mono<ServerResponse> body(Object body, Class<?> bodyType);
277
278
Mono<ServerResponse> render(String name, Object... modelAttributes);
279
Mono<ServerResponse> render(String name, Map<String, ?> model);
280
}
281
}
282
```
283
284
## Usage Examples
285
286
### Basic Router Configuration
287
288
```java
289
@Configuration
290
public class RouterConfiguration {
291
292
@Bean
293
public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
294
return RouterFunctions.route()
295
.GET("/api/users", handler::listUsers)
296
.GET("/api/users/{id}", handler::getUser)
297
.POST("/api/users", handler::createUser)
298
.PUT("/api/users/{id}", handler::updateUser)
299
.DELETE("/api/users/{id}", handler::deleteUser)
300
.build();
301
}
302
}
303
```
304
305
### Handler Implementation
306
307
```java
308
@Component
309
public class UserHandler {
310
311
private final UserService userService;
312
313
public UserHandler(UserService userService) {
314
this.userService = userService;
315
}
316
317
public Mono<ServerResponse> listUsers(ServerRequest request) {
318
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
319
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
320
321
return ServerResponse.ok()
322
.contentType(MediaType.APPLICATION_JSON)
323
.body(userService.findAll(page, size), User.class);
324
}
325
326
public Mono<ServerResponse> getUser(ServerRequest request) {
327
String id = request.pathVariable("id");
328
329
return userService.findById(id)
330
.flatMap(user -> ServerResponse.ok()
331
.contentType(MediaType.APPLICATION_JSON)
332
.bodyValue(user))
333
.switchIfEmpty(ServerResponse.notFound().build());
334
}
335
336
public Mono<ServerResponse> createUser(ServerRequest request) {
337
return request.bodyToMono(User.class)
338
.flatMap(userService::save)
339
.flatMap(user -> ServerResponse.created(URI.create("/api/users/" + user.getId()))
340
.contentType(MediaType.APPLICATION_JSON)
341
.bodyValue(user));
342
}
343
344
public Mono<ServerResponse> updateUser(ServerRequest request) {
345
String id = request.pathVariable("id");
346
347
return request.bodyToMono(User.class)
348
.flatMap(user -> userService.update(id, user))
349
.flatMap(user -> ServerResponse.ok()
350
.contentType(MediaType.APPLICATION_JSON)
351
.bodyValue(user))
352
.switchIfEmpty(ServerResponse.notFound().build());
353
}
354
355
public Mono<ServerResponse> deleteUser(ServerRequest request) {
356
String id = request.pathVariable("id");
357
358
return userService.deleteById(id)
359
.flatMap(deleted -> deleted ?
360
ServerResponse.noContent().build() :
361
ServerResponse.notFound().build());
362
}
363
}
364
```
365
366
### Advanced Routing with Filters
367
368
```java
369
@Configuration
370
public class AdvancedRouterConfiguration {
371
372
@Bean
373
public RouterFunction<ServerResponse> apiRoutes(UserHandler userHandler, OrderHandler orderHandler) {
374
return RouterFunctions.route()
375
.path("/api", builder -> builder
376
.before(this::logRequest)
377
.after(this::addSecurityHeaders)
378
.filter(this::authenticate)
379
.nest(path("/users"), userRoutes(userHandler))
380
.nest(path("/orders"), orderRoutes(orderHandler))
381
)
382
.onError(ValidationException.class, this::handleValidationError)
383
.onError(ResourceNotFoundException.class, this::handleNotFound)
384
.build();
385
}
386
387
private RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
388
return RouterFunctions.route()
389
.GET("", handler::listUsers)
390
.GET("/{id}", handler::getUser)
391
.POST("", handler::createUser)
392
.PUT("/{id}", handler::updateUser)
393
.DELETE("/{id}", handler::deleteUser)
394
.build();
395
}
396
397
private RouterFunction<ServerResponse> orderRoutes(OrderHandler handler) {
398
return RouterFunctions.route()
399
.GET("", handler::listOrders)
400
.GET("/{id}", handler::getOrder)
401
.POST("", handler::createOrder)
402
.build();
403
}
404
405
private ServerRequest logRequest(ServerRequest request) {
406
log.info("Processing request: {} {}", request.method(), request.uri());
407
return request;
408
}
409
410
private ServerResponse addSecurityHeaders(ServerRequest request, ServerResponse response) {
411
return ServerResponse.from(response)
412
.header("X-Content-Type-Options", "nosniff")
413
.header("X-Frame-Options", "DENY")
414
.build()
415
.block();
416
}
417
418
private Mono<ServerResponse> authenticate(ServerRequest request, HandlerFunction<ServerResponse> next) {
419
return extractToken(request)
420
.flatMap(this::validateToken)
421
.flatMap(principal -> next.handle(request.mutate()
422
.attribute("principal", principal)
423
.build()))
424
.switchIfEmpty(ServerResponse.status(HttpStatus.UNAUTHORIZED).build());
425
}
426
427
private Mono<ServerResponse> handleValidationError(Throwable ex, ServerRequest request) {
428
return ServerResponse.badRequest()
429
.contentType(MediaType.APPLICATION_JSON)
430
.bodyValue(Map.of("error", "VALIDATION_ERROR", "message", ex.getMessage()));
431
}
432
433
private Mono<ServerResponse> handleNotFound(Throwable ex, ServerRequest request) {
434
return ServerResponse.notFound().build();
435
}
436
}
437
```
438
439
### Content Negotiation
440
441
```java
442
@Component
443
public class ContentHandler {
444
445
public Mono<ServerResponse> getData(ServerRequest request) {
446
List<MediaType> acceptableTypes = request.headers().accept();
447
448
if (acceptableTypes.contains(MediaType.APPLICATION_JSON)) {
449
return ServerResponse.ok()
450
.contentType(MediaType.APPLICATION_JSON)
451
.body(dataService.getJsonData(), JsonData.class);
452
} else if (acceptableTypes.contains(MediaType.APPLICATION_XML)) {
453
return ServerResponse.ok()
454
.contentType(MediaType.APPLICATION_XML)
455
.body(dataService.getXmlData(), XmlData.class);
456
} else {
457
return ServerResponse.status(HttpStatus.NOT_ACCEPTABLE).build();
458
}
459
}
460
461
public Mono<ServerResponse> streamData(ServerRequest request) {
462
return ServerResponse.ok()
463
.contentType(MediaType.TEXT_EVENT_STREAM)
464
.body(Flux.interval(Duration.ofSeconds(1))
465
.map(i -> "data: Event " + i + "\n\n"), String.class);
466
}
467
}
468
```
469
470
### File Handling
471
472
```java
473
@Component
474
public class FileHandler {
475
476
public Mono<ServerResponse> uploadFile(ServerRequest request) {
477
return request.multipartData()
478
.map(parts -> parts.get("file"))
479
.cast(List<Part>.class)
480
.flatMap(parts -> {
481
if (parts.isEmpty()) {
482
return ServerResponse.badRequest()
483
.bodyValue("No file provided");
484
}
485
486
Part filePart = parts.get(0);
487
if (filePart instanceof FilePart) {
488
FilePart file = (FilePart) filePart;
489
return file.transferTo(Paths.get("/tmp/" + file.filename()))
490
.then(ServerResponse.ok().bodyValue("File uploaded: " + file.filename()));
491
}
492
493
return ServerResponse.badRequest().bodyValue("Invalid file");
494
});
495
}
496
497
public Mono<ServerResponse> downloadFile(ServerRequest request) {
498
String filename = request.pathVariable("filename");
499
Path filePath = Paths.get("/files/" + filename);
500
501
if (!Files.exists(filePath)) {
502
return ServerResponse.notFound().build();
503
}
504
505
Resource resource = new FileSystemResource(filePath);
506
return ServerResponse.ok()
507
.contentType(MediaType.APPLICATION_OCTET_STREAM)
508
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
509
.body(BodyInserters.fromResource(resource));
510
}
511
}
512
```
513
514
## Composition and Modularity
515
516
Functional routing excels at composition, allowing you to build complex routing structures from simple, reusable components:
517
518
```java
519
public class ModularRouting {
520
521
public static RouterFunction<ServerResponse> publicRoutes() {
522
return RouterFunctions.route()
523
.GET("/health", request -> ServerResponse.ok().bodyValue("OK"))
524
.GET("/info", request -> ServerResponse.ok().bodyValue("App Info"))
525
.build();
526
}
527
528
public static RouterFunction<ServerResponse> securedRoutes(AuthHandler authHandler) {
529
return RouterFunctions.route()
530
.filter(authHandler::authenticate)
531
.GET("/profile", authHandler::getProfile)
532
.POST("/logout", authHandler::logout)
533
.build();
534
}
535
536
@Bean
537
public RouterFunction<ServerResponse> allRoutes(AuthHandler authHandler) {
538
return publicRoutes()
539
.and(securedRoutes(authHandler))
540
.filter(this::corsFilter)
541
.filter(this::loggingFilter);
542
}
543
}
544
```