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

configuration.mddocs/

0

# Configuration

1

2

Spring Boot WebFlux provides comprehensive configuration options through Spring Boot properties, programmatic configuration classes, and customization interfaces. This enables fine-tuning of reactive web behavior, static resources, server settings, and WebFlux-specific features.

3

4

## WebFlux Configuration Properties

5

6

### Core WebFlux Properties

7

8

```java { .api }

9

@ConfigurationProperties("spring.webflux")

10

public class WebFluxProperties {

11

12

private String basePath;

13

private String staticPathPattern = "/**";

14

private String webjarsPathPattern = "/webjars/**";

15

private final Format format = new Format();

16

private final Problemdetails problemdetails = new Problemdetails();

17

18

public static class Format {

19

private String date;

20

private String time;

21

private String dateTime = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";

22

}

23

24

public static class Problemdetails {

25

private boolean enabled = false;

26

}

27

}

28

```

29

30

### HTTP Codecs Configuration Properties

31

32

```java { .api }

33

@ConfigurationProperties("spring.http.codecs")

34

public class HttpCodecsProperties {

35

36

private DataSize maxInMemorySize = DataSize.ofBytes(262144); // 256KB

37

private boolean logRequestDetails = false;

38

39

public DataSize getMaxInMemorySize();

40

public void setMaxInMemorySize(DataSize maxInMemorySize);

41

public boolean isLogRequestDetails();

42

public void setLogRequestDetails(boolean logRequestDetails);

43

}

44

```

45

46

## WebFlux Configuration Interface

47

48

### WebFluxConfigurer

49

50

```java { .api }

51

public interface WebFluxConfigurer {

52

53

default void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {}

54

55

default void addCorsMappings(CorsRegistry registry) {}

56

57

default void configurePathMatching(PathMatchConfigurer configurer) {}

58

59

default void addResourceHandlers(ResourceHandlerRegistry registry) {}

60

61

default void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {}

62

63

default void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {}

64

65

default void addFormatters(FormatterRegistry registry) {}

66

67

default Validator getValidator() { return null; }

68

69

default MessageCodesResolver getMessageCodesResolver() { return null; }

70

71

default void configureViewResolvers(ViewResolverRegistry registry) {}

72

}

73

```

74

75

### WebFluxConfiguration Implementation

76

77

```java

78

@Configuration

79

@EnableWebFlux

80

public class WebFluxConfiguration implements WebFluxConfigurer {

81

82

@Override

83

public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {

84

configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024); // 2MB

85

configurer.defaultCodecs().enableLoggingRequestDetails(true);

86

87

// Custom JSON encoder/decoder

88

configurer.defaultCodecs().jackson2JsonEncoder(customJsonEncoder());

89

configurer.defaultCodecs().jackson2JsonDecoder(customJsonDecoder());

90

91

// Custom string encoder for CSV

92

configurer.customCodecs().register(new CsvEncoder());

93

configurer.customCodecs().register(new CsvDecoder());

94

95

// Configure multipart reader with DataBuffer handling

96

configurer.defaultCodecs().multipartReader(multipartHttpMessageReader());

97

98

// Configure server-sent events with streaming

99

configurer.defaultCodecs().serverSentEventHttpMessageReader(sseReader());

100

}

101

102

@Override

103

public void addCorsMappings(CorsRegistry registry) {

104

registry.addMapping("/api/**")

105

.allowedOrigins("http://localhost:3000", "https://app.example.com")

106

.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")

107

.allowedHeaders("*")

108

.allowCredentials(true)

109

.maxAge(3600);

110

}

111

112

@Override

113

public void addResourceHandlers(ResourceHandlerRegistry registry) {

114

registry.addResourceHandler("/static/**")

115

.addResourceLocations("classpath:/static/")

116

.setCacheControl(CacheControl.maxAge(Duration.ofDays(7)));

117

118

registry.addResourceHandler("/uploads/**")

119

.addResourceLocations("file:./uploads/")

120

.setCacheControl(CacheControl.noCache());

121

}

122

123

@Override

124

public void configurePathMatching(PathMatchConfigurer configurer) {

125

configurer.setUseCaseSensitiveMatch(false)

126

.setUseTrailingSlashMatch(true)

127

.addPathPrefix("/api/v1", HandlerTypePredicate.forAnnotation(RestController.class));

128

}

129

130

@Override

131

public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {

132

configurer.addCustomResolver(new CustomArgumentResolver());

133

}

134

135

@Override

136

public void addFormatters(FormatterRegistry registry) {

137

registry.addConverter(new StringToDateConverter());

138

registry.addFormatter(new LocalDateTimeFormatter());

139

}

140

141

@Override

142

public Validator getValidator() {

143

return new LocalValidatorFactoryBean();

144

}

145

146

@Bean

147

public Jackson2JsonEncoder customJsonEncoder() {

148

ObjectMapper mapper = new ObjectMapper();

149

mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

150

mapper.registerModule(new JavaTimeModule());

151

return new Jackson2JsonEncoder(mapper);

152

}

153

154

@Bean

155

public Jackson2JsonDecoder customJsonDecoder() {

156

ObjectMapper mapper = new ObjectMapper();

157

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

158

mapper.registerModule(new JavaTimeModule());

159

return new Jackson2JsonDecoder(mapper);

160

}

161

}

162

```

163

164

## Session Management Configuration

165

166

### WebSessionManager Interface

167

168

```java { .api }

169

public interface WebSessionManager {

170

171

Mono<WebSession> getSession(ServerWebExchange exchange);

172

173

default WebSessionIdResolver getSessionIdResolver() {

174

return new CookieWebSessionIdResolver();

175

}

176

}

177

```

178

179

### DefaultWebSessionManager

180

181

```java { .api }

182

public class DefaultWebSessionManager implements WebSessionManager {

183

184

private WebSessionIdResolver sessionIdResolver = new CookieWebSessionIdResolver();

185

private WebSessionStore sessionStore = new InMemoryWebSessionStore();

186

187

public void setSessionIdResolver(WebSessionIdResolver sessionIdResolver);

188

public WebSessionIdResolver getSessionIdResolver();

189

public void setSessionStore(WebSessionStore sessionStore);

190

public WebSessionStore getSessionStore();

191

192

@Override

193

public Mono<WebSession> getSession(ServerWebExchange exchange);

194

}

195

```

196

197

### WebSessionStore Interface

198

199

```java { .api }

200

public interface WebSessionStore {

201

202

Mono<WebSession> createWebSession();

203

Mono<WebSession> retrieveSession(String sessionId);

204

Mono<Void> removeSession(String sessionId);

205

Mono<WebSession> updateLastAccessTime(WebSession webSession);

206

}

207

```

208

209

### InMemoryWebSessionStore

210

211

```java { .api }

212

public class InMemoryWebSessionStore implements WebSessionStore {

213

214

private int maxSessions = 10000;

215

private Duration maxInactiveInterval = Duration.ofMinutes(30);

216

217

public void setMaxSessions(int maxSessions);

218

public int getMaxSessions();

219

public void setMaxInactiveInterval(Duration maxInactiveInterval);

220

public Duration getMaxInactiveInterval();

221

222

@Override

223

public Mono<WebSession> createWebSession();

224

@Override

225

public Mono<WebSession> retrieveSession(String sessionId);

226

@Override

227

public Mono<Void> removeSession(String sessionId);

228

@Override

229

public Mono<WebSession> updateLastAccessTime(WebSession webSession);

230

}

231

```

232

233

### Session Configuration Example

234

235

```java

236

@Configuration

237

public class SessionConfiguration {

238

239

@Bean

240

public WebSessionManager webSessionManager() {

241

DefaultWebSessionManager manager = new DefaultWebSessionManager();

242

243

// Configure session ID resolver

244

CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();

245

resolver.setCookieName("JSESSIONID");

246

resolver.addCookieInitializer(builder -> {

247

builder.httpOnly(true)

248

.secure(true)

249

.sameSite("Strict")

250

.maxAge(Duration.ofHours(24));

251

});

252

manager.setSessionIdResolver(resolver);

253

254

// Configure session store

255

InMemoryWebSessionStore store = new InMemoryWebSessionStore();

256

store.setMaxSessions(5000);

257

store.setMaxInactiveInterval(Duration.ofMinutes(45));

258

manager.setSessionStore(store);

259

260

return manager;

261

}

262

}

263

```

264

265

## Locale Resolution Configuration

266

267

### LocaleContextResolver Interface

268

269

```java { .api }

270

public interface LocaleContextResolver {

271

272

LocaleContext resolveLocaleContext(ServerWebExchange exchange);

273

void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext);

274

}

275

```

276

277

### AcceptHeaderLocaleContextResolver

278

279

```java { .api }

280

public class AcceptHeaderLocaleContextResolver implements LocaleContextResolver {

281

282

private List<Locale> supportedLocales = new ArrayList<>();

283

private Locale defaultLocale;

284

285

public void setSupportedLocales(List<Locale> locales);

286

public List<Locale> getSupportedLocales();

287

public void setDefaultLocale(Locale defaultLocale);

288

public Locale getDefaultLocale();

289

290

@Override

291

public LocaleContext resolveLocaleContext(ServerWebExchange exchange);

292

@Override

293

public void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext);

294

}

295

```

296

297

### FixedLocaleContextResolver

298

299

```java { .api }

300

public class FixedLocaleContextResolver implements LocaleContextResolver {

301

302

private Locale defaultLocale = Locale.getDefault();

303

304

public void setDefaultLocale(Locale defaultLocale);

305

public Locale getDefaultLocale();

306

307

@Override

308

public LocaleContext resolveLocaleContext(ServerWebExchange exchange);

309

@Override

310

public void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext);

311

}

312

```

313

314

### Locale Configuration Example

315

316

```java

317

@Configuration

318

public class LocaleConfiguration {

319

320

@Bean

321

public LocaleContextResolver localeContextResolver() {

322

AcceptHeaderLocaleContextResolver resolver = new AcceptHeaderLocaleContextResolver();

323

resolver.setSupportedLocales(Arrays.asList(

324

Locale.ENGLISH,

325

Locale.FRENCH,

326

Locale.GERMAN,

327

new Locale("es")

328

));

329

resolver.setDefaultLocale(Locale.ENGLISH);

330

return resolver;

331

}

332

}

333

```

334

335

## WebFilter Configuration

336

337

### WebFilter Interface

338

339

```java { .api }

340

public interface WebFilter {

341

342

Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);

343

}

344

```

345

346

### WebFilterChain Interface

347

348

```java { .api }

349

public interface WebFilterChain {

350

351

Mono<Void> filter(ServerWebExchange exchange);

352

}

353

```

354

355

### Common WebFilter Implementations

356

357

```java { .api }

358

public class OrderedHiddenHttpMethodFilter implements WebFilter, Ordered {

359

360

public static final int DEFAULT_ORDER = REQUEST_WRAPPER_FILTER_MAX_ORDER - 10000;

361

private int order = DEFAULT_ORDER;

362

private String methodParam = "_method";

363

364

public void setMethodParam(String methodParam);

365

public String getMethodParam();

366

public void setOrder(int order);

367

@Override

368

public int getOrder();

369

370

@Override

371

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);

372

}

373

374

public class ForwardedHeaderFilter implements WebFilter {

375

376

public static final int REQUEST_WRAPPER_FILTER_MAX_ORDER = 0;

377

378

@Override

379

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);

380

}

381

```

382

383

### Custom WebFilter Example

384

385

```java

386

@Component

387

public class RequestLoggingFilter implements WebFilter {

388

389

private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);

390

391

@Override

392

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

393

ServerHttpRequest request = exchange.getRequest();

394

395

logger.info("Processing request: {} {}",

396

request.getMethod(),

397

request.getURI());

398

399

return chain.filter(exchange)

400

.doOnSuccess(aVoid -> {

401

ServerHttpResponse response = exchange.getResponse();

402

logger.info("Response status: {}", response.getStatusCode());

403

})

404

.doOnError(throwable -> {

405

logger.error("Request processing failed", throwable);

406

});

407

}

408

}

409

410

@Component

411

@Order(Ordered.HIGHEST_PRECEDENCE)

412

public class CorsWebFilter implements WebFilter {

413

414

@Override

415

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

416

ServerHttpRequest request = exchange.getRequest();

417

ServerHttpResponse response = exchange.getResponse();

418

419

HttpHeaders responseHeaders = response.getHeaders();

420

421

// Add CORS headers

422

responseHeaders.add("Access-Control-Allow-Origin", "*");

423

responseHeaders.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");

424

responseHeaders.add("Access-Control-Allow-Headers", "Content-Type, Authorization");

425

responseHeaders.add("Access-Control-Max-Age", "3600");

426

427

if (HttpMethod.OPTIONS.equals(request.getMethod())) {

428

response.setStatusCode(HttpStatus.OK);

429

return Mono.empty();

430

}

431

432

return chain.filter(exchange);

433

}

434

}

435

436

@Component

437

public class RequestTimingFilter implements WebFilter {

438

439

private static final String REQUEST_START_TIME = "requestStartTime";

440

441

@Override

442

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

443

exchange.getAttributes().put(REQUEST_START_TIME, System.currentTimeMillis());

444

445

return chain.filter(exchange)

446

.doFinally(signalType -> {

447

Long startTime = exchange.getAttribute(REQUEST_START_TIME);

448

if (startTime != null) {

449

long duration = System.currentTimeMillis() - startTime;

450

ServerHttpRequest request = exchange.getRequest();

451

System.out.println(String.format("Request %s %s took %d ms",

452

request.getMethod(),

453

request.getURI(),

454

duration));

455

}

456

});

457

}

458

}

459

```

460

461

## ServerCodecConfigurer and DataBuffer Usage

462

463

### ServerCodecConfigurer Interface

464

465

```java { .api }

466

public interface ServerCodecConfigurer extends CodecConfigurer {

467

468

ServerDefaultCodecs defaultCodecs();

469

void clone(Consumer<ServerCodecConfigurer> consumer);

470

471

interface ServerDefaultCodecs extends DefaultCodecs {

472

void multipartReader(HttpMessageReader<?> reader);

473

void multipartCodecs(Consumer<MultipartHttpMessageReader.Builder> consumer);

474

void serverSentEventHttpMessageReader(HttpMessageReader<?> reader);

475

}

476

}

477

```

478

479

### DataBuffer and DataBufferFactory

480

481

```java { .api }

482

public interface DataBuffer {

483

484

DataBufferFactory factory();

485

int indexOf(IntPredicate predicate, int fromIndex);

486

int lastIndexOf(IntPredicate predicate, int fromIndex);

487

int readableByteCount();

488

int writableByteCount();

489

int capacity();

490

DataBuffer capacity(int capacity);

491

DataBuffer ensureCapacity(int capacity);

492

int readPosition();

493

DataBuffer readPosition(int readPosition);

494

int writePosition();

495

DataBuffer writePosition(int writePosition);

496

497

byte getByte(int index);

498

byte read();

499

DataBuffer read(byte[] destination);

500

DataBuffer read(byte[] destination, int offset, int length);

501

DataBuffer write(byte b);

502

DataBuffer write(byte[] source);

503

DataBuffer write(byte[] source, int offset, int length);

504

DataBuffer write(DataBuffer... buffers);

505

DataBuffer write(ByteBuffer... buffers);

506

DataBuffer slice(int index, int length);

507

DataBuffer retainedSlice(int index, int length);

508

ByteBuffer asByteBuffer();

509

ByteBuffer asByteBuffer(int index, int length);

510

InputStream asInputStream();

511

InputStream asInputStream(boolean releaseOnClose);

512

OutputStream asOutputStream();

513

String toString(Charset charset);

514

String toString(int index, int length, Charset charset);

515

}

516

517

public interface DataBufferFactory {

518

519

DataBuffer allocateBuffer();

520

DataBuffer allocateBuffer(int initialCapacity);

521

DataBuffer wrap(ByteBuffer byteBuffer);

522

DataBuffer wrap(byte[] bytes);

523

DataBuffer join(List<? extends DataBuffer> dataBuffers);

524

boolean isDirect();

525

}

526

```

527

528

### DataBuffer Usage Examples

529

530

```java

531

@Component

532

public class DataBufferProcessor {

533

534

private final DataBufferFactory bufferFactory;

535

536

public DataBufferProcessor(DataBufferFactory bufferFactory) {

537

this.bufferFactory = bufferFactory;

538

}

539

540

// Stream processing with DataBuffer

541

public Flux<DataBuffer> processLargeFile(String filePath) {

542

return DataBufferUtils.readInputStream(

543

() -> new FileInputStream(filePath),

544

bufferFactory,

545

4096

546

).map(this::processChunk);

547

}

548

549

private DataBuffer processChunk(DataBuffer buffer) {

550

// Process the data buffer

551

byte[] bytes = new byte[buffer.readableByteCount()];

552

buffer.read(bytes);

553

554

// Transform the data (example: to uppercase)

555

String content = new String(bytes, StandardCharsets.UTF_8);

556

String processed = content.toUpperCase();

557

558

// Release the original buffer

559

DataBufferUtils.release(buffer);

560

561

// Return new buffer with processed content

562

return bufferFactory.wrap(processed.getBytes(StandardCharsets.UTF_8));

563

}

564

565

// Streaming response with DataBuffer

566

public Mono<ServerResponse> streamLargeResponse() {

567

Flux<DataBuffer> dataStream = Flux.range(1, 1000)

568

.map(i -> "Data chunk " + i + "\n")

569

.map(s -> bufferFactory.wrap(s.getBytes(StandardCharsets.UTF_8)));

570

571

return ServerResponse.ok()

572

.contentType(MediaType.APPLICATION_OCTET_STREAM)

573

.body(dataStream, DataBuffer.class);

574

}

575

576

// Multipart file handling with DataBuffer

577

public Mono<String> handleFileUpload(Flux<DataBuffer> content) {

578

return DataBufferUtils.join(content)

579

.map(buffer -> {

580

try {

581

byte[] bytes = new byte[buffer.readableByteCount()];

582

buffer.read(bytes);

583

return new String(bytes, StandardCharsets.UTF_8);

584

} finally {

585

DataBufferUtils.release(buffer);

586

}

587

});

588

}

589

}

590

591

@Component

592

public class CustomCodecConfiguration {

593

594

@Bean

595

public HttpMessageReader<MultiValueMap<String, Part>> multipartReader() {

596

return new MultipartHttpMessageReader();

597

}

598

599

@Bean

600

public HttpMessageReader<Object> sseReader() {

601

return new ServerSentEventHttpMessageReader();

602

}

603

604

@Bean

605

public Encoder<CustomData> customDataEncoder() {

606

return new AbstractEncoder<CustomData>(MediaType.APPLICATION_JSON) {

607

@Override

608

public Flux<DataBuffer> encode(Publisher<? extends CustomData> inputStream,

609

DataBufferFactory bufferFactory,

610

ResolvableType elementType,

611

MimeType mimeType,

612

Map<String, Object> hints) {

613

return Flux.from(inputStream)

614

.map(this::serialize)

615

.map(json -> bufferFactory.wrap(json.getBytes(StandardCharsets.UTF_8)));

616

}

617

618

private String serialize(CustomData data) {

619

// Custom serialization logic

620

return "{\"id\":\"" + data.getId() + "\",\"value\":\"" + data.getValue() + "\"}";

621

}

622

};

623

}

624

625

@Bean

626

public Decoder<CustomData> customDataDecoder() {

627

return new AbstractDecoder<CustomData>(MediaType.APPLICATION_JSON) {

628

@Override

629

public Flux<CustomData> decode(Publisher<DataBuffer> inputStream,

630

ResolvableType elementType,

631

MimeType mimeType,

632

Map<String, Object> hints) {

633

return Flux.from(inputStream)

634

.reduce(DataBuffer::write)

635

.map(buffer -> {

636

try {

637

byte[] bytes = new byte[buffer.readableByteCount()];

638

buffer.read(bytes);

639

String json = new String(bytes, StandardCharsets.UTF_8);

640

return deserialize(json);

641

} finally {

642

DataBufferUtils.release(buffer);

643

}

644

})

645

.flux();

646

}

647

648

private CustomData deserialize(String json) {

649

// Custom deserialization logic

650

// This is a simplified example - use proper JSON parsing in production

651

return new CustomData();

652

}

653

};

654

}

655

}

656

657

// Custom data class for the example

658

public class CustomData {

659

private String id;

660

private String value;

661

662

// constructors, getters, setters

663

public CustomData() {}

664

665

public String getId() { return id; }

666

public void setId(String id) { this.id = id; }

667

public String getValue() { return value; }

668

public void setValue(String value) { this.value = value; }

669

}

670

```

671

672

### Advanced ServerCodecConfigurer Usage

673

674

```java

675

@Configuration

676

public class AdvancedCodecConfiguration implements WebFluxConfigurer {

677

678

@Override

679

public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {

680

// Configure maximum in-memory size for data buffers

681

configurer.defaultCodecs().maxInMemorySize(8 * 1024 * 1024); // 8MB

682

683

// Enable logging of request details

684

configurer.defaultCodecs().enableLoggingRequestDetails(true);

685

686

// Configure multipart handling

687

configurer.defaultCodecs().multipartCodecs(builder -> {

688

builder.maxInMemorySize(2 * 1024 * 1024) // 2MB per part

689

.maxHeadersSize(8192) // 8KB headers

690

.maxParts(20); // Maximum 20 parts

691

});

692

693

// Register custom encoders/decoders

694

configurer.customCodecs().register(new ProtobufEncoder());

695

configurer.customCodecs().register(new ProtobufDecoder());

696

697

// Configure Jackson JSON processing

698

configurer.defaultCodecs().jackson2JsonEncoder(

699

new Jackson2JsonEncoder(customObjectMapper())

700

);

701

configurer.defaultCodecs().jackson2JsonDecoder(

702

new Jackson2JsonDecoder(customObjectMapper())

703

);

704

705

// Configure string encoder/decoder with specific charsets

706

configurer.defaultCodecs().stringEncoder(

707

CharSequenceEncoder.textPlainOnly(List.of(StandardCharsets.UTF_8))

708

);

709

configurer.defaultCodecs().stringDecoder(

710

StringDecoder.textPlainOnly(List.of(StandardCharsets.UTF_8), false)

711

);

712

}

713

714

@Bean

715

public ObjectMapper customObjectMapper() {

716

ObjectMapper mapper = new ObjectMapper();

717

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

718

mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

719

mapper.registerModule(new JavaTimeModule());

720

return mapper;

721

}

722

723

@Bean

724

public DataBufferFactory dataBufferFactory() {

725

// Choose between DefaultDataBufferFactory and NettyDataBufferFactory

726

return new DefaultDataBufferFactory();

727

}

728

}

729

```

730

731

## Customization Interfaces

732

733

### WebFluxRegistrations

734

735

```java { .api }

736

public interface WebFluxRegistrations {

737

738

default RequestMappingHandlerMapping getRequestMappingHandlerMapping() {

739

return null;

740

}

741

742

default RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {

743

return null;

744

}

745

}

746

```

747

748

### Custom Registrations Implementation

749

750

```java

751

@Configuration

752

public class CustomWebFluxRegistrations implements WebFluxRegistrations {

753

754

@Override

755

public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {

756

RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();

757

mapping.setOrder(0);

758

mapping.setUseCaseSensitiveMatch(false);

759

mapping.setUseTrailingSlashMatch(true);

760

return mapping;

761

}

762

763

@Override

764

public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {

765

RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();

766

adapter.setIgnoreDefaultModelOnRedirect(true);

767

return adapter;

768

}

769

}

770

```

771

772

### ResourceHandlerRegistrationCustomizer

773

774

```java { .api }

775

@FunctionalInterface

776

public interface ResourceHandlerRegistrationCustomizer {

777

void customize(ResourceHandlerRegistration registration);

778

}

779

```

780

781

### Custom Resource Handler

782

783

```java

784

@Configuration

785

public class ResourceConfiguration {

786

787

@Bean

788

public ResourceHandlerRegistrationCustomizer resourceCustomizer() {

789

return registration -> {

790

registration.setCacheControl(CacheControl.maxAge(Duration.ofDays(30)))

791

.resourceChain(true)

792

.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))

793

.addTransformer(new CssLinkResourceTransformer());

794

};

795

}

796

797

@Bean

798

public ResourceUrlProvider resourceUrlProvider() {

799

return new ResourceUrlProvider();

800

}

801

802

@Bean

803

public WebFluxConfigurer staticResourceConfigurer(ResourceUrlProvider resourceUrlProvider) {

804

return new WebFluxConfigurerAdapter() {

805

@Override

806

public void addResourceHandlers(ResourceHandlerRegistry registry) {

807

registry.addResourceHandler("/assets/**")

808

.addResourceLocations("classpath:/assets/")

809

.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)))

810

.resourceChain(true)

811

.addResolver(new VersionResourceResolver()

812

.addContentVersionStrategy("/**"))

813

.addTransformer(new AppCacheManifestTransformer());

814

}

815

};

816

}

817

}

818

```

819

820

## Codec Configuration

821

822

### ServerCodecConfigurer

823

824

```java { .api }

825

public interface ServerCodecConfigurer extends CodecConfigurer {

826

827

ServerDefaultCodecs defaultCodecs();

828

void clone(Consumer<ServerCodecConfigurer> consumer);

829

830

interface ServerDefaultCodecs extends DefaultCodecs {

831

void serverSentEventEncoder(Encoder<?> encoder);

832

void multipartReader(HttpMessageReader<?> reader);

833

void multipartCodecs();

834

}

835

}

836

```

837

838

### Custom Codec Configuration

839

840

```java

841

@Configuration

842

public class CodecConfiguration {

843

844

@Bean

845

public CodecCustomizer codecCustomizer() {

846

return configurer -> {

847

// Increase max in-memory size

848

configurer.defaultCodecs().maxInMemorySize(5 * 1024 * 1024); // 5MB

849

850

// Enable request logging

851

configurer.defaultCodecs().enableLoggingRequestDetails(true);

852

853

// Custom JSON codec with specific ObjectMapper

854

ObjectMapper mapper = createCustomObjectMapper();

855

configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper));

856

configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper));

857

858

// Custom protobuf codec

859

configurer.defaultCodecs().protobufDecoder(new ProtobufDecoder());

860

configurer.defaultCodecs().protobufEncoder(new ProtobufHttpMessageWriter());

861

862

// Server-sent events configuration

863

configurer.defaultCodecs().serverSentEventEncoder(new Jackson2JsonEncoder());

864

};

865

}

866

867

@Bean

868

public ObjectMapper createCustomObjectMapper() {

869

return JsonMapper.builder()

870

.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)

871

.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

872

.addModule(new JavaTimeModule())

873

.addModule(new Jdk8Module())

874

.build();

875

}

876

877

@Bean

878

public HttpMessageReader<?> customMultipartReader() {

879

return new MultipartHttpMessageReader(new DefaultPartHttpMessageReader());

880

}

881

882

@Bean

883

public HttpMessageWriter<?> customMultipartWriter() {

884

return new MultipartHttpMessageWriter();

885

}

886

}

887

```

888

889

## CORS Configuration

890

891

### Global CORS Configuration

892

893

```java

894

@Configuration

895

public class CorsConfiguration {

896

897

@Bean

898

public CorsConfigurationSource corsConfigurationSource() {

899

CorsConfiguration configuration = new CorsConfiguration();

900

configuration.setAllowedOriginPatterns(Arrays.asList("http://localhost:*", "https://*.example.com"));

901

configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));

902

configuration.setAllowedHeaders(Arrays.asList("*"));

903

configuration.setAllowCredentials(true);

904

configuration.setMaxAge(3600L);

905

906

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

907

source.registerCorsConfiguration("/api/**", configuration);

908

return source;

909

}

910

911

@Bean

912

public CorsWebFilter corsWebFilter() {

913

return new CorsWebFilter(corsConfigurationSource());

914

}

915

}

916

```

917

918

### Annotation-Based CORS

919

920

```java

921

@RestController

922

@CrossOrigin(origins = "http://localhost:3000", maxAge = 3600)

923

public class ApiController {

924

925

@CrossOrigin(origins = "https://admin.example.com")

926

@GetMapping("/admin/users")

927

public Flux<User> getAdminUsers() {

928

return userService.findAll();

929

}

930

931

@CrossOrigin(methods = {RequestMethod.POST, RequestMethod.PUT})

932

@PostMapping("/users")

933

public Mono<User> createUser(@RequestBody Mono<User> user) {

934

return user.flatMap(userService::save);

935

}

936

}

937

```

938

939

## Filter Configuration

940

941

### WebFilter Implementation

942

943

```java

944

@Component

945

@Order(Ordered.HIGHEST_PRECEDENCE)

946

public class RequestLoggingFilter implements WebFilter {

947

948

private final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);

949

950

@Override

951

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

952

ServerHttpRequest request = exchange.getRequest();

953

String requestId = UUID.randomUUID().toString();

954

955

// Add request ID to response headers

956

exchange.getResponse().getHeaders().add("X-Request-ID", requestId);

957

958

logger.info("Request started: {} {} [{}]",

959

request.getMethod(), request.getURI(), requestId);

960

961

long startTime = System.currentTimeMillis();

962

963

return chain.filter(exchange)

964

.doFinally(signalType -> {

965

long duration = System.currentTimeMillis() - startTime;

966

ServerHttpResponse response = exchange.getResponse();

967

logger.info("Request completed: {} {} {} {}ms [{}]",

968

request.getMethod(), request.getURI(),

969

response.getStatusCode(), duration, requestId);

970

});

971

}

972

}

973

```

974

975

### Security Filter

976

977

```java

978

@Component

979

public class AuthenticationFilter implements WebFilter {

980

981

private final AuthenticationService authService;

982

983

public AuthenticationFilter(AuthenticationService authService) {

984

this.authService = authService;

985

}

986

987

@Override

988

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

989

ServerHttpRequest request = exchange.getRequest();

990

991

// Skip authentication for public endpoints

992

if (isPublicEndpoint(request.getPath().value())) {

993

return chain.filter(exchange);

994

}

995

996

String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);

997

if (authHeader == null || !authHeader.startsWith("Bearer ")) {

998

return handleUnauthorized(exchange);

999

}

1000

1001

String token = authHeader.substring(7);

1002

return authService.validateToken(token)

1003

.flatMap(user -> {

1004

ServerWebExchange mutatedExchange = exchange.mutate()

1005

.request(request.mutate()

1006

.header("X-User-ID", user.getId())

1007

.header("X-User-Role", user.getRole())

1008

.build())

1009

.build();

1010

return chain.filter(mutatedExchange);

1011

})

1012

.switchIfEmpty(handleUnauthorized(exchange));

1013

}

1014

1015

private boolean isPublicEndpoint(String path) {

1016

return path.startsWith("/public/") ||

1017

path.equals("/health") ||

1018

path.equals("/metrics");

1019

}

1020

1021

private Mono<Void> handleUnauthorized(ServerWebExchange exchange) {

1022

ServerHttpResponse response = exchange.getResponse();

1023

response.setStatusCode(HttpStatus.UNAUTHORIZED);

1024

response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

1025

1026

String body = "{\"error\":\"UNAUTHORIZED\",\"message\":\"Authentication required\"}";

1027

DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));

1028

return response.writeWith(Mono.just(buffer));

1029

}

1030

}

1031

```

1032

1033

## Validation Configuration

1034

1035

### Custom Validator Configuration

1036

1037

```java

1038

@Configuration

1039

public class ValidationConfiguration {

1040

1041

@Bean

1042

public LocalValidatorFactoryBean validator() {

1043

LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();

1044

validator.setProviderClass(HibernateValidator.class);

1045

validator.setMessageInterpolator(new ParameterMessageInterpolator());

1046

return validator;

1047

}

1048

1049

@Bean

1050

public MethodValidationPostProcessor methodValidationPostProcessor() {

1051

MethodValidationPostProcessor processor = new MethodValidationPostProcessor();

1052

processor.setValidator(validator());

1053

return processor;

1054

}

1055

1056

@Bean

1057

public WebFluxConfigurer validationConfigurer() {

1058

return new WebFluxConfigurerAdapter() {

1059

@Override

1060

public Validator getValidator() {

1061

return validator();

1062

}

1063

};

1064

}

1065

}

1066

```

1067

1068

### Custom Validation Groups

1069

1070

```java

1071

public interface CreateValidation {}

1072

public interface UpdateValidation {}

1073

1074

@RestController

1075

@Validated

1076

public class UserController {

1077

1078

@PostMapping("/users")

1079

public Mono<User> createUser(@Validated(CreateValidation.class) @RequestBody Mono<User> user) {

1080

return user.flatMap(userService::save);

1081

}

1082

1083

@PutMapping("/users/{id}")

1084

public Mono<User> updateUser(@PathVariable String id,

1085

@Validated(UpdateValidation.class) @RequestBody Mono<User> user) {

1086

return user.flatMap(u -> userService.update(id, u));

1087

}

1088

}

1089

1090

public class User {

1091

@NotNull(groups = {CreateValidation.class, UpdateValidation.class})

1092

@Size(min = 2, max = 50, groups = {CreateValidation.class, UpdateValidation.class})

1093

private String name;

1094

1095

@NotNull(groups = CreateValidation.class)

1096

@Email(groups = {CreateValidation.class, UpdateValidation.class})

1097

private String email;

1098

1099

@Null(groups = CreateValidation.class) // ID should be null when creating

1100

@NotNull(groups = UpdateValidation.class) // ID should be present when updating

1101

private String id;

1102

1103

// Constructors, getters, setters...

1104

}

1105

```

1106

1107

## Properties Configuration Examples

1108

1109

### Application Properties

1110

1111

```properties

1112

# WebFlux Configuration

1113

spring.webflux.base-path=/api/v1

1114

spring.webflux.static-path-pattern=/static/**

1115

spring.webflux.webjars-path-pattern=/webjars/**

1116

spring.webflux.hiddenmethod.filter.enabled=true

1117

spring.webflux.format.date-time=yyyy-MM-dd'T'HH:mm:ss.SSSXXX

1118

spring.webflux.problemdetails.enabled=true

1119

1120

# HTTP Codecs Configuration

1121

spring.http.codecs.max-in-memory-size=2MB

1122

spring.http.codecs.log-request-details=true

1123

1124

# Server Configuration

1125

server.port=8080

1126

server.address=0.0.0.0

1127

server.http2.enabled=true

1128

server.compression.enabled=true

1129

server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml

1130

server.compression.min-response-size=2048

1131

1132

# Netty-specific configuration

1133

server.netty.connection-timeout=20s

1134

server.netty.idle-timeout=60s

1135

server.netty.max-keep-alive-requests=100

1136

1137

# SSL Configuration

1138

server.ssl.enabled=true

1139

server.ssl.key-store=classpath:keystore.p12

1140

server.ssl.key-store-password=password

1141

server.ssl.key-store-type=PKCS12

1142

server.ssl.key-alias=spring

1143

server.ssl.protocol=TLS

1144

server.ssl.enabled-protocols=TLSv1.2,TLSv1.3

1145

1146

# Logging

1147

logging.level.org.springframework.web.reactive=DEBUG

1148

logging.level.org.springframework.http.server.reactive=DEBUG

1149

logging.level.org.springframework.web.reactive.function.client=DEBUG

1150

```

1151

1152

### YAML Configuration

1153

1154

```yaml

1155

spring:

1156

webflux:

1157

base-path: /api/v1

1158

static-path-pattern: /static/**

1159

format:

1160

date-time: "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"

1161

problemdetails:

1162

enabled: true

1163

1164

http:

1165

codecs:

1166

max-in-memory-size: 2MB

1167

log-request-details: true

1168

1169

server:

1170

port: 8080

1171

http2:

1172

enabled: true

1173

compression:

1174

enabled: true

1175

mime-types:

1176

- text/html

1177

- text/xml

1178

- text/plain

1179

- text/css

1180

- application/json

1181

- application/javascript

1182

min-response-size: 2KB

1183

1184

netty:

1185

connection-timeout: 20s

1186

idle-timeout: 60s

1187

max-keep-alive-requests: 100

1188

1189

ssl:

1190

enabled: true

1191

key-store: classpath:keystore.p12

1192

key-store-password: password

1193

key-store-type: PKCS12

1194

key-alias: spring

1195

protocol: TLS

1196

enabled-protocols:

1197

- TLSv1.2

1198

- TLSv1.3

1199

1200

logging:

1201

level:

1202

org.springframework.web.reactive: DEBUG

1203

org.springframework.http.server.reactive: DEBUG

1204

```

1205

1206

## Profile-Specific Configuration

1207

1208

### Development Profile

1209

1210

```yaml

1211

# application-dev.yml

1212

spring:

1213

webflux:

1214

problemdetails:

1215

enabled: true

1216

http:

1217

codecs:

1218

log-request-details: true

1219

1220

server:

1221

port: 8080

1222

ssl:

1223

enabled: false

1224

1225

logging:

1226

level:

1227

org.springframework.web.reactive: DEBUG

1228

reactor.netty.http.server: DEBUG

1229

root: INFO

1230

```

1231

1232

### Production Profile

1233

1234

```yaml

1235

# application-prod.yml

1236

spring:

1237

webflux:

1238

problemdetails:

1239

enabled: false

1240

http:

1241

codecs:

1242

log-request-details: false

1243

1244

server:

1245

port: 8443

1246

ssl:

1247

enabled: true

1248

key-store: file:/etc/ssl/app-keystore.p12

1249

key-store-password: ${SSL_KEYSTORE_PASSWORD}

1250

compression:

1251

enabled: true

1252

shutdown: graceful

1253

1254

management:

1255

endpoints:

1256

web:

1257

exposure:

1258

include: health,metrics,prometheus

1259

endpoint:

1260

health:

1261

show-details: never

1262

1263

logging:

1264

level:

1265

org.springframework.web.reactive: WARN

1266

root: INFO

1267

pattern:

1268

console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

1269

```