CtrlK
BlogDocsLog inGet started
Tessl Logo

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

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

Pending
Overview
Eval results
Files

configuration.mddocs/

Configuration

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.

WebFlux Configuration Properties

Core WebFlux Properties

@ConfigurationProperties("spring.webflux")
public class WebFluxProperties {
    
    private String basePath;
    private String staticPathPattern = "/**";
    private String webjarsPathPattern = "/webjars/**";
    private final Format format = new Format();
    private final Problemdetails problemdetails = new Problemdetails();
    
    public static class Format {
        private String date;
        private String time;
        private String dateTime = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
    }
    
    public static class Problemdetails {
        private boolean enabled = false;
    }
}

HTTP Codecs Configuration Properties

@ConfigurationProperties("spring.http.codecs")
public class HttpCodecsProperties {
    
    private DataSize maxInMemorySize = DataSize.ofBytes(262144); // 256KB
    private boolean logRequestDetails = false;
    
    public DataSize getMaxInMemorySize();
    public void setMaxInMemorySize(DataSize maxInMemorySize);
    public boolean isLogRequestDetails();
    public void setLogRequestDetails(boolean logRequestDetails);
}

WebFlux Configuration Interface

WebFluxConfigurer

public interface WebFluxConfigurer {
    
    default void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {}
    
    default void addCorsMappings(CorsRegistry registry) {}
    
    default void configurePathMatching(PathMatchConfigurer configurer) {}
    
    default void addResourceHandlers(ResourceHandlerRegistry registry) {}
    
    default void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {}
    
    default void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {}
    
    default void addFormatters(FormatterRegistry registry) {}
    
    default Validator getValidator() { return null; }
    
    default MessageCodesResolver getMessageCodesResolver() { return null; }
    
    default void configureViewResolvers(ViewResolverRegistry registry) {}
}

WebFluxConfiguration Implementation

@Configuration
@EnableWebFlux
public class WebFluxConfiguration implements WebFluxConfigurer {
    
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024); // 2MB
        configurer.defaultCodecs().enableLoggingRequestDetails(true);
        
        // Custom JSON encoder/decoder
        configurer.defaultCodecs().jackson2JsonEncoder(customJsonEncoder());
        configurer.defaultCodecs().jackson2JsonDecoder(customJsonDecoder());
        
        // Custom string encoder for CSV
        configurer.customCodecs().register(new CsvEncoder());
        configurer.customCodecs().register(new CsvDecoder());
        
        // Configure multipart reader with DataBuffer handling
        configurer.defaultCodecs().multipartReader(multipartHttpMessageReader());
        
        // Configure server-sent events with streaming
        configurer.defaultCodecs().serverSentEventHttpMessageReader(sseReader());
    }
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("http://localhost:3000", "https://app.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
            .addResourceLocations("classpath:/static/")
            .setCacheControl(CacheControl.maxAge(Duration.ofDays(7)));
        
        registry.addResourceHandler("/uploads/**")
            .addResourceLocations("file:./uploads/")
            .setCacheControl(CacheControl.noCache());
    }
    
    @Override
    public void configurePathMatching(PathMatchConfigurer configurer) {
        configurer.setUseCaseSensitiveMatch(false)
            .setUseTrailingSlashMatch(true)
            .addPathPrefix("/api/v1", HandlerTypePredicate.forAnnotation(RestController.class));
    }
    
    @Override
    public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
        configurer.addCustomResolver(new CustomArgumentResolver());
    }
    
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToDateConverter());
        registry.addFormatter(new LocalDateTimeFormatter());
    }
    
    @Override
    public Validator getValidator() {
        return new LocalValidatorFactoryBean();
    }
    
    @Bean
    public Jackson2JsonEncoder customJsonEncoder() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.registerModule(new JavaTimeModule());
        return new Jackson2JsonEncoder(mapper);
    }
    
    @Bean
    public Jackson2JsonDecoder customJsonDecoder() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.registerModule(new JavaTimeModule());
        return new Jackson2JsonDecoder(mapper);
    }
}

Session Management Configuration

WebSessionManager Interface

public interface WebSessionManager {
    
    Mono<WebSession> getSession(ServerWebExchange exchange);
    
    default WebSessionIdResolver getSessionIdResolver() {
        return new CookieWebSessionIdResolver();
    }
}

DefaultWebSessionManager

public class DefaultWebSessionManager implements WebSessionManager {
    
    private WebSessionIdResolver sessionIdResolver = new CookieWebSessionIdResolver();
    private WebSessionStore sessionStore = new InMemoryWebSessionStore();
    
    public void setSessionIdResolver(WebSessionIdResolver sessionIdResolver);
    public WebSessionIdResolver getSessionIdResolver();
    public void setSessionStore(WebSessionStore sessionStore);
    public WebSessionStore getSessionStore();
    
    @Override
    public Mono<WebSession> getSession(ServerWebExchange exchange);
}

WebSessionStore Interface

public interface WebSessionStore {
    
    Mono<WebSession> createWebSession();
    Mono<WebSession> retrieveSession(String sessionId);
    Mono<Void> removeSession(String sessionId);
    Mono<WebSession> updateLastAccessTime(WebSession webSession);
}

InMemoryWebSessionStore

public class InMemoryWebSessionStore implements WebSessionStore {
    
    private int maxSessions = 10000;
    private Duration maxInactiveInterval = Duration.ofMinutes(30);
    
    public void setMaxSessions(int maxSessions);
    public int getMaxSessions();
    public void setMaxInactiveInterval(Duration maxInactiveInterval);
    public Duration getMaxInactiveInterval();
    
    @Override
    public Mono<WebSession> createWebSession();
    @Override
    public Mono<WebSession> retrieveSession(String sessionId);
    @Override
    public Mono<Void> removeSession(String sessionId);
    @Override
    public Mono<WebSession> updateLastAccessTime(WebSession webSession);
}

Session Configuration Example

@Configuration
public class SessionConfiguration {
    
    @Bean
    public WebSessionManager webSessionManager() {
        DefaultWebSessionManager manager = new DefaultWebSessionManager();
        
        // Configure session ID resolver
        CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
        resolver.setCookieName("JSESSIONID");
        resolver.addCookieInitializer(builder -> {
            builder.httpOnly(true)
                   .secure(true)
                   .sameSite("Strict")
                   .maxAge(Duration.ofHours(24));
        });
        manager.setSessionIdResolver(resolver);
        
        // Configure session store
        InMemoryWebSessionStore store = new InMemoryWebSessionStore();
        store.setMaxSessions(5000);
        store.setMaxInactiveInterval(Duration.ofMinutes(45));
        manager.setSessionStore(store);
        
        return manager;
    }
}

Locale Resolution Configuration

LocaleContextResolver Interface

public interface LocaleContextResolver {
    
    LocaleContext resolveLocaleContext(ServerWebExchange exchange);
    void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext);
}

AcceptHeaderLocaleContextResolver

public class AcceptHeaderLocaleContextResolver implements LocaleContextResolver {
    
    private List<Locale> supportedLocales = new ArrayList<>();
    private Locale defaultLocale;
    
    public void setSupportedLocales(List<Locale> locales);
    public List<Locale> getSupportedLocales();
    public void setDefaultLocale(Locale defaultLocale);
    public Locale getDefaultLocale();
    
    @Override
    public LocaleContext resolveLocaleContext(ServerWebExchange exchange);
    @Override
    public void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext);
}

FixedLocaleContextResolver

public class FixedLocaleContextResolver implements LocaleContextResolver {
    
    private Locale defaultLocale = Locale.getDefault();
    
    public void setDefaultLocale(Locale defaultLocale);
    public Locale getDefaultLocale();
    
    @Override
    public LocaleContext resolveLocaleContext(ServerWebExchange exchange);
    @Override
    public void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext);
}

Locale Configuration Example

@Configuration
public class LocaleConfiguration {
    
    @Bean
    public LocaleContextResolver localeContextResolver() {
        AcceptHeaderLocaleContextResolver resolver = new AcceptHeaderLocaleContextResolver();
        resolver.setSupportedLocales(Arrays.asList(
            Locale.ENGLISH,
            Locale.FRENCH,
            Locale.GERMAN,
            new Locale("es")
        ));
        resolver.setDefaultLocale(Locale.ENGLISH);
        return resolver;
    }
}

WebFilter Configuration

WebFilter Interface

public interface WebFilter {
    
    Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
}

WebFilterChain Interface

public interface WebFilterChain {
    
    Mono<Void> filter(ServerWebExchange exchange);
}

Common WebFilter Implementations

public class OrderedHiddenHttpMethodFilter implements WebFilter, Ordered {
    
    public static final int DEFAULT_ORDER = REQUEST_WRAPPER_FILTER_MAX_ORDER - 10000;
    private int order = DEFAULT_ORDER;
    private String methodParam = "_method";
    
    public void setMethodParam(String methodParam);
    public String getMethodParam();
    public void setOrder(int order);
    @Override
    public int getOrder();
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
}

public class ForwardedHeaderFilter implements WebFilter {
    
    public static final int REQUEST_WRAPPER_FILTER_MAX_ORDER = 0;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
}

Custom WebFilter Example

@Component
public class RequestLoggingFilter implements WebFilter {
    
    private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        
        logger.info("Processing request: {} {}", 
                   request.getMethod(), 
                   request.getURI());
        
        return chain.filter(exchange)
            .doOnSuccess(aVoid -> {
                ServerHttpResponse response = exchange.getResponse();
                logger.info("Response status: {}", response.getStatusCode());
            })
            .doOnError(throwable -> {
                logger.error("Request processing failed", throwable);
            });
    }
}

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsWebFilter implements WebFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        
        HttpHeaders responseHeaders = response.getHeaders();
        
        // Add CORS headers
        responseHeaders.add("Access-Control-Allow-Origin", "*");
        responseHeaders.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        responseHeaders.add("Access-Control-Allow-Headers", "Content-Type, Authorization");
        responseHeaders.add("Access-Control-Max-Age", "3600");
        
        if (HttpMethod.OPTIONS.equals(request.getMethod())) {
            response.setStatusCode(HttpStatus.OK);
            return Mono.empty();
        }
        
        return chain.filter(exchange);
    }
}

@Component
public class RequestTimingFilter implements WebFilter {
    
    private static final String REQUEST_START_TIME = "requestStartTime";
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        exchange.getAttributes().put(REQUEST_START_TIME, System.currentTimeMillis());
        
        return chain.filter(exchange)
            .doFinally(signalType -> {
                Long startTime = exchange.getAttribute(REQUEST_START_TIME);
                if (startTime != null) {
                    long duration = System.currentTimeMillis() - startTime;
                    ServerHttpRequest request = exchange.getRequest();
                    System.out.println(String.format("Request %s %s took %d ms", 
                                                   request.getMethod(), 
                                                   request.getURI(), 
                                                   duration));
                }
            });
    }
}

ServerCodecConfigurer and DataBuffer Usage

ServerCodecConfigurer Interface

public interface ServerCodecConfigurer extends CodecConfigurer {
    
    ServerDefaultCodecs defaultCodecs();
    void clone(Consumer<ServerCodecConfigurer> consumer);
    
    interface ServerDefaultCodecs extends DefaultCodecs {
        void multipartReader(HttpMessageReader<?> reader);
        void multipartCodecs(Consumer<MultipartHttpMessageReader.Builder> consumer);
        void serverSentEventHttpMessageReader(HttpMessageReader<?> reader);
    }
}

DataBuffer and DataBufferFactory

public interface DataBuffer {
    
    DataBufferFactory factory();
    int indexOf(IntPredicate predicate, int fromIndex);
    int lastIndexOf(IntPredicate predicate, int fromIndex);
    int readableByteCount();
    int writableByteCount();
    int capacity();
    DataBuffer capacity(int capacity);
    DataBuffer ensureCapacity(int capacity);
    int readPosition();
    DataBuffer readPosition(int readPosition);
    int writePosition();
    DataBuffer writePosition(int writePosition);
    
    byte getByte(int index);
    byte read();
    DataBuffer read(byte[] destination);
    DataBuffer read(byte[] destination, int offset, int length);
    DataBuffer write(byte b);
    DataBuffer write(byte[] source);
    DataBuffer write(byte[] source, int offset, int length);
    DataBuffer write(DataBuffer... buffers);
    DataBuffer write(ByteBuffer... buffers);
    DataBuffer slice(int index, int length);
    DataBuffer retainedSlice(int index, int length);
    ByteBuffer asByteBuffer();
    ByteBuffer asByteBuffer(int index, int length);
    InputStream asInputStream();
    InputStream asInputStream(boolean releaseOnClose);
    OutputStream asOutputStream();
    String toString(Charset charset);
    String toString(int index, int length, Charset charset);
}

public interface DataBufferFactory {
    
    DataBuffer allocateBuffer();
    DataBuffer allocateBuffer(int initialCapacity);
    DataBuffer wrap(ByteBuffer byteBuffer);
    DataBuffer wrap(byte[] bytes);
    DataBuffer join(List<? extends DataBuffer> dataBuffers);
    boolean isDirect();
}

DataBuffer Usage Examples

@Component
public class DataBufferProcessor {
    
    private final DataBufferFactory bufferFactory;
    
    public DataBufferProcessor(DataBufferFactory bufferFactory) {
        this.bufferFactory = bufferFactory;
    }
    
    // Stream processing with DataBuffer
    public Flux<DataBuffer> processLargeFile(String filePath) {
        return DataBufferUtils.readInputStream(
            () -> new FileInputStream(filePath),
            bufferFactory,
            4096
        ).map(this::processChunk);
    }
    
    private DataBuffer processChunk(DataBuffer buffer) {
        // Process the data buffer
        byte[] bytes = new byte[buffer.readableByteCount()];
        buffer.read(bytes);
        
        // Transform the data (example: to uppercase)
        String content = new String(bytes, StandardCharsets.UTF_8);
        String processed = content.toUpperCase();
        
        // Release the original buffer
        DataBufferUtils.release(buffer);
        
        // Return new buffer with processed content
        return bufferFactory.wrap(processed.getBytes(StandardCharsets.UTF_8));
    }
    
    // Streaming response with DataBuffer
    public Mono<ServerResponse> streamLargeResponse() {
        Flux<DataBuffer> dataStream = Flux.range(1, 1000)
            .map(i -> "Data chunk " + i + "\n")
            .map(s -> bufferFactory.wrap(s.getBytes(StandardCharsets.UTF_8)));
        
        return ServerResponse.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(dataStream, DataBuffer.class);
    }
    
    // Multipart file handling with DataBuffer
    public Mono<String> handleFileUpload(Flux<DataBuffer> content) {
        return DataBufferUtils.join(content)
            .map(buffer -> {
                try {
                    byte[] bytes = new byte[buffer.readableByteCount()];
                    buffer.read(bytes);
                    return new String(bytes, StandardCharsets.UTF_8);
                } finally {
                    DataBufferUtils.release(buffer);
                }
            });
    }
}

@Component
public class CustomCodecConfiguration {
    
    @Bean
    public HttpMessageReader<MultiValueMap<String, Part>> multipartReader() {
        return new MultipartHttpMessageReader();
    }
    
    @Bean
    public HttpMessageReader<Object> sseReader() {
        return new ServerSentEventHttpMessageReader();
    }
    
    @Bean
    public Encoder<CustomData> customDataEncoder() {
        return new AbstractEncoder<CustomData>(MediaType.APPLICATION_JSON) {
            @Override
            public Flux<DataBuffer> encode(Publisher<? extends CustomData> inputStream,
                                         DataBufferFactory bufferFactory,
                                         ResolvableType elementType,
                                         MimeType mimeType,
                                         Map<String, Object> hints) {
                return Flux.from(inputStream)
                    .map(this::serialize)
                    .map(json -> bufferFactory.wrap(json.getBytes(StandardCharsets.UTF_8)));
            }
            
            private String serialize(CustomData data) {
                // Custom serialization logic
                return "{\"id\":\"" + data.getId() + "\",\"value\":\"" + data.getValue() + "\"}";
            }
        };
    }
    
    @Bean
    public Decoder<CustomData> customDataDecoder() {
        return new AbstractDecoder<CustomData>(MediaType.APPLICATION_JSON) {
            @Override
            public Flux<CustomData> decode(Publisher<DataBuffer> inputStream,
                                         ResolvableType elementType,
                                         MimeType mimeType,
                                         Map<String, Object> hints) {
                return Flux.from(inputStream)
                    .reduce(DataBuffer::write)
                    .map(buffer -> {
                        try {
                            byte[] bytes = new byte[buffer.readableByteCount()];
                            buffer.read(bytes);
                            String json = new String(bytes, StandardCharsets.UTF_8);
                            return deserialize(json);
                        } finally {
                            DataBufferUtils.release(buffer);
                        }
                    })
                    .flux();
            }
            
            private CustomData deserialize(String json) {
                // Custom deserialization logic
                // This is a simplified example - use proper JSON parsing in production
                return new CustomData();
            }
        };
    }
}

// Custom data class for the example
public class CustomData {
    private String id;
    private String value;
    
    // constructors, getters, setters
    public CustomData() {}
    
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getValue() { return value; }
    public void setValue(String value) { this.value = value; }
}

Advanced ServerCodecConfigurer Usage

@Configuration
public class AdvancedCodecConfiguration implements WebFluxConfigurer {
    
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // Configure maximum in-memory size for data buffers
        configurer.defaultCodecs().maxInMemorySize(8 * 1024 * 1024); // 8MB
        
        // Enable logging of request details
        configurer.defaultCodecs().enableLoggingRequestDetails(true);
        
        // Configure multipart handling
        configurer.defaultCodecs().multipartCodecs(builder -> {
            builder.maxInMemorySize(2 * 1024 * 1024) // 2MB per part
                   .maxHeadersSize(8192) // 8KB headers
                   .maxParts(20); // Maximum 20 parts
        });
        
        // Register custom encoders/decoders
        configurer.customCodecs().register(new ProtobufEncoder());
        configurer.customCodecs().register(new ProtobufDecoder());
        
        // Configure Jackson JSON processing
        configurer.defaultCodecs().jackson2JsonEncoder(
            new Jackson2JsonEncoder(customObjectMapper())
        );
        configurer.defaultCodecs().jackson2JsonDecoder(
            new Jackson2JsonDecoder(customObjectMapper())
        );
        
        // Configure string encoder/decoder with specific charsets
        configurer.defaultCodecs().stringEncoder(
            CharSequenceEncoder.textPlainOnly(List.of(StandardCharsets.UTF_8))
        );
        configurer.defaultCodecs().stringDecoder(
            StringDecoder.textPlainOnly(List.of(StandardCharsets.UTF_8), false)
        );
    }
    
    @Bean
    public ObjectMapper customObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.registerModule(new JavaTimeModule());
        return mapper;
    }
    
    @Bean
    public DataBufferFactory dataBufferFactory() {
        // Choose between DefaultDataBufferFactory and NettyDataBufferFactory
        return new DefaultDataBufferFactory();
    }
}

Customization Interfaces

WebFluxRegistrations

public interface WebFluxRegistrations {
    
    default RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return null;
    }
    
    default RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
        return null;
    }
}

Custom Registrations Implementation

@Configuration
public class CustomWebFluxRegistrations implements WebFluxRegistrations {
    
    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
        mapping.setOrder(0);
        mapping.setUseCaseSensitiveMatch(false);
        mapping.setUseTrailingSlashMatch(true);
        return mapping;
    }
    
    @Override
    public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
        adapter.setIgnoreDefaultModelOnRedirect(true);
        return adapter;
    }
}

ResourceHandlerRegistrationCustomizer

@FunctionalInterface
public interface ResourceHandlerRegistrationCustomizer {
    void customize(ResourceHandlerRegistration registration);
}

Custom Resource Handler

@Configuration
public class ResourceConfiguration {
    
    @Bean
    public ResourceHandlerRegistrationCustomizer resourceCustomizer() {
        return registration -> {
            registration.setCacheControl(CacheControl.maxAge(Duration.ofDays(30)))
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
                .addTransformer(new CssLinkResourceTransformer());
        };
    }
    
    @Bean
    public ResourceUrlProvider resourceUrlProvider() {
        return new ResourceUrlProvider();
    }
    
    @Bean
    public WebFluxConfigurer staticResourceConfigurer(ResourceUrlProvider resourceUrlProvider) {
        return new WebFluxConfigurerAdapter() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/assets/**")
                    .addResourceLocations("classpath:/assets/")
                    .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)))
                    .resourceChain(true)
                    .addResolver(new VersionResourceResolver()
                        .addContentVersionStrategy("/**"))
                    .addTransformer(new AppCacheManifestTransformer());
            }
        };
    }
}

Codec Configuration

ServerCodecConfigurer

public interface ServerCodecConfigurer extends CodecConfigurer {
    
    ServerDefaultCodecs defaultCodecs();
    void clone(Consumer<ServerCodecConfigurer> consumer);
    
    interface ServerDefaultCodecs extends DefaultCodecs {
        void serverSentEventEncoder(Encoder<?> encoder);
        void multipartReader(HttpMessageReader<?> reader);
        void multipartCodecs();
    }
}

Custom Codec Configuration

@Configuration
public class CodecConfiguration {
    
    @Bean
    public CodecCustomizer codecCustomizer() {
        return configurer -> {
            // Increase max in-memory size
            configurer.defaultCodecs().maxInMemorySize(5 * 1024 * 1024); // 5MB
            
            // Enable request logging
            configurer.defaultCodecs().enableLoggingRequestDetails(true);
            
            // Custom JSON codec with specific ObjectMapper
            ObjectMapper mapper = createCustomObjectMapper();
            configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper));
            configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper));
            
            // Custom protobuf codec
            configurer.defaultCodecs().protobufDecoder(new ProtobufDecoder());
            configurer.defaultCodecs().protobufEncoder(new ProtobufHttpMessageWriter());
            
            // Server-sent events configuration
            configurer.defaultCodecs().serverSentEventEncoder(new Jackson2JsonEncoder());
        };
    }
    
    @Bean
    public ObjectMapper createCustomObjectMapper() {
        return JsonMapper.builder()
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .addModule(new JavaTimeModule())
            .addModule(new Jdk8Module())
            .build();
    }
    
    @Bean
    public HttpMessageReader<?> customMultipartReader() {
        return new MultipartHttpMessageReader(new DefaultPartHttpMessageReader());
    }
    
    @Bean
    public HttpMessageWriter<?> customMultipartWriter() {
        return new MultipartHttpMessageWriter();
    }
}

CORS Configuration

Global CORS Configuration

@Configuration
public class CorsConfiguration {
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Arrays.asList("http://localhost:*", "https://*.example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);
        return source;
    }
    
    @Bean
    public CorsWebFilter corsWebFilter() {
        return new CorsWebFilter(corsConfigurationSource());
    }
}

Annotation-Based CORS

@RestController
@CrossOrigin(origins = "http://localhost:3000", maxAge = 3600)
public class ApiController {
    
    @CrossOrigin(origins = "https://admin.example.com")
    @GetMapping("/admin/users")
    public Flux<User> getAdminUsers() {
        return userService.findAll();
    }
    
    @CrossOrigin(methods = {RequestMethod.POST, RequestMethod.PUT})
    @PostMapping("/users")
    public Mono<User> createUser(@RequestBody Mono<User> user) {
        return user.flatMap(userService::save);
    }
}

Filter Configuration

WebFilter Implementation

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RequestLoggingFilter implements WebFilter {
    
    private final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String requestId = UUID.randomUUID().toString();
        
        // Add request ID to response headers
        exchange.getResponse().getHeaders().add("X-Request-ID", requestId);
        
        logger.info("Request started: {} {} [{}]", 
            request.getMethod(), request.getURI(), requestId);
        
        long startTime = System.currentTimeMillis();
        
        return chain.filter(exchange)
            .doFinally(signalType -> {
                long duration = System.currentTimeMillis() - startTime;
                ServerHttpResponse response = exchange.getResponse();
                logger.info("Request completed: {} {} {} {}ms [{}]",
                    request.getMethod(), request.getURI(), 
                    response.getStatusCode(), duration, requestId);
            });
    }
}

Security Filter

@Component
public class AuthenticationFilter implements WebFilter {
    
    private final AuthenticationService authService;
    
    public AuthenticationFilter(AuthenticationService authService) {
        this.authService = authService;
    }
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        
        // Skip authentication for public endpoints
        if (isPublicEndpoint(request.getPath().value())) {
            return chain.filter(exchange);
        }
        
        String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return handleUnauthorized(exchange);
        }
        
        String token = authHeader.substring(7);
        return authService.validateToken(token)
            .flatMap(user -> {
                ServerWebExchange mutatedExchange = exchange.mutate()
                    .request(request.mutate()
                        .header("X-User-ID", user.getId())
                        .header("X-User-Role", user.getRole())
                        .build())
                    .build();
                return chain.filter(mutatedExchange);
            })
            .switchIfEmpty(handleUnauthorized(exchange));
    }
    
    private boolean isPublicEndpoint(String path) {
        return path.startsWith("/public/") || 
               path.equals("/health") || 
               path.equals("/metrics");
    }
    
    private Mono<Void> handleUnauthorized(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        
        String body = "{\"error\":\"UNAUTHORIZED\",\"message\":\"Authentication required\"}";
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
}

Validation Configuration

Custom Validator Configuration

@Configuration
public class ValidationConfiguration {
    
    @Bean
    public LocalValidatorFactoryBean validator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setProviderClass(HibernateValidator.class);
        validator.setMessageInterpolator(new ParameterMessageInterpolator());
        return validator;
    }
    
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(validator());
        return processor;
    }
    
    @Bean
    public WebFluxConfigurer validationConfigurer() {
        return new WebFluxConfigurerAdapter() {
            @Override
            public Validator getValidator() {
                return validator();
            }
        };
    }
}

Custom Validation Groups

public interface CreateValidation {}
public interface UpdateValidation {}

@RestController
@Validated
public class UserController {
    
    @PostMapping("/users")
    public Mono<User> createUser(@Validated(CreateValidation.class) @RequestBody Mono<User> user) {
        return user.flatMap(userService::save);
    }
    
    @PutMapping("/users/{id}")
    public Mono<User> updateUser(@PathVariable String id, 
                                @Validated(UpdateValidation.class) @RequestBody Mono<User> user) {
        return user.flatMap(u -> userService.update(id, u));
    }
}

public class User {
    @NotNull(groups = {CreateValidation.class, UpdateValidation.class})
    @Size(min = 2, max = 50, groups = {CreateValidation.class, UpdateValidation.class})
    private String name;
    
    @NotNull(groups = CreateValidation.class)
    @Email(groups = {CreateValidation.class, UpdateValidation.class})
    private String email;
    
    @Null(groups = CreateValidation.class) // ID should be null when creating
    @NotNull(groups = UpdateValidation.class) // ID should be present when updating
    private String id;
    
    // Constructors, getters, setters...
}

Properties Configuration Examples

Application Properties

# WebFlux Configuration
spring.webflux.base-path=/api/v1
spring.webflux.static-path-pattern=/static/**
spring.webflux.webjars-path-pattern=/webjars/**
spring.webflux.hiddenmethod.filter.enabled=true
spring.webflux.format.date-time=yyyy-MM-dd'T'HH:mm:ss.SSSXXX
spring.webflux.problemdetails.enabled=true

# HTTP Codecs Configuration  
spring.http.codecs.max-in-memory-size=2MB
spring.http.codecs.log-request-details=true

# Server Configuration
server.port=8080
server.address=0.0.0.0
server.http2.enabled=true
server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml
server.compression.min-response-size=2048

# Netty-specific configuration
server.netty.connection-timeout=20s
server.netty.idle-timeout=60s
server.netty.max-keep-alive-requests=100

# SSL Configuration
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=spring
server.ssl.protocol=TLS
server.ssl.enabled-protocols=TLSv1.2,TLSv1.3

# Logging
logging.level.org.springframework.web.reactive=DEBUG
logging.level.org.springframework.http.server.reactive=DEBUG
logging.level.org.springframework.web.reactive.function.client=DEBUG

YAML Configuration

spring:
  webflux:
    base-path: /api/v1
    static-path-pattern: /static/**
    format:
      date-time: "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"
    problemdetails:
      enabled: true
  
  http:
    codecs:
      max-in-memory-size: 2MB
      log-request-details: true

server:
  port: 8080
  http2:
    enabled: true
  compression:
    enabled: true
    mime-types:
      - text/html
      - text/xml
      - text/plain
      - text/css
      - application/json
      - application/javascript
    min-response-size: 2KB
  
  netty:
    connection-timeout: 20s
    idle-timeout: 60s
    max-keep-alive-requests: 100
  
  ssl:
    enabled: true
    key-store: classpath:keystore.p12
    key-store-password: password
    key-store-type: PKCS12
    key-alias: spring
    protocol: TLS
    enabled-protocols:
      - TLSv1.2
      - TLSv1.3

logging:
  level:
    org.springframework.web.reactive: DEBUG
    org.springframework.http.server.reactive: DEBUG

Profile-Specific Configuration

Development Profile

# application-dev.yml
spring:
  webflux:
    problemdetails:
      enabled: true
  http:
    codecs:
      log-request-details: true

server:
  port: 8080
  ssl:
    enabled: false

logging:
  level:
    org.springframework.web.reactive: DEBUG
    reactor.netty.http.server: DEBUG
    root: INFO

Production Profile

# application-prod.yml
spring:
  webflux:
    problemdetails:
      enabled: false
  http:
    codecs:
      log-request-details: false

server:
  port: 8443
  ssl:
    enabled: true
    key-store: file:/etc/ssl/app-keystore.p12
    key-store-password: ${SSL_KEYSTORE_PASSWORD}
  compression:
    enabled: true
  shutdown: graceful

management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus
  endpoint:
    health:
      show-details: never

logging:
  level:
    org.springframework.web.reactive: WARN
    root: INFO
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

Install with Tessl CLI

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

docs

annotation-controllers.md

configuration.md

error-handling.md

functional-routing.md

index.md

server-configuration.md

testing.md

webclient.md

tile.json