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
```