or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotation-controllers.mdconfiguration.mderror-handling.mdfunctional-routing.mdindex.mdserver-configuration.mdtesting.mdwebclient.md

functional-routing.mddocs/

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

```