or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

annotation-controllers.mdconfiguration.mdcontent-negotiation.mdexception-handling.mdfunctional-routing.mdindex.mdresource-handling.mdview-rendering.mdwebclient.mdwebsocket.md
tile.json

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

Spring WebFlux reactive web framework for building non-blocking, reactive web applications

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/org.springframework/spring-webflux@7.0.x

To install, run

npx @tessl/cli install tessl/maven-org-springframework--spring-webflux@7.0.0

index.mddocs/

Spring WebFlux

Spring WebFlux is a reactive-stack web framework for building non-blocking, reactive web applications with Reactive Streams back pressure.

Package Information

  • Maven Coordinates: org.springframework:spring-webflux:7.0.1
  • Language: Java
  • Requires: Spring Framework 7.0.1+, Reactor Core 3.6+

Installation:

Maven:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
    <version>7.0.1</version>
</dependency>

Gradle:

implementation 'org.springframework:spring-webflux:7.0.1'

Quick Start

Minimal Annotation Controller

import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/hello")
    public Mono<String> hello() {
        return Mono.just("Hello, WebFlux!");
    }

    @PostMapping("/users")
    public Mono<User> createUser(@RequestBody User user) {
        return userRepository.save(user);
    }
}

Minimal Functional Route

import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.server.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

@Bean
public RouterFunction<ServerResponse> routes() {
    return route(GET("/hello"),
        request -> ServerResponse.ok().bodyValue("Hello, WebFlux!"));
}

Minimal WebClient Call

import org.springframework.web.reactive.function.client.WebClient;

WebClient client = WebClient.create("https://api.example.com");
Mono<User> user = client.get()
    .uri("/users/{id}", userId)
    .retrieve()
    .bodyToMono(User.class);

Core Imports

// Annotations
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.config.EnableWebFlux;

// Functional Routing
import org.springframework.web.reactive.function.server.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.*;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

// WebClient
import org.springframework.web.reactive.function.client.WebClient;

// Reactive Types
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;

// Exception Handling
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.server.ResponseStatusException;

// WebSocket
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import org.springframework.web.reactive.socket.WebSocketMessage;

Architecture Overview

Two Programming Models:

  1. Annotation Controllers: @RestController, @RequestMapping, method annotations
  2. Functional Endpoints: RouterFunction, HandlerFunction, composable routes

Request Processing Flow:

Request → DispatcherHandler → HandlerMapping → HandlerAdapter → Handler → Result Handler → Response

Core Components:

  • DispatcherHandler: Central request dispatcher
  • HandlerMapping: Routes requests to handlers
  • HandlerAdapter: Invokes handlers, processes arguments/return values
  • HandlerResultHandler: Writes handler results to response

Reactive Foundation:

  • Built on Project Reactor (Mono<T> for 0-1 items, Flux<T> for 0-N items)
  • Non-blocking, backpressure-aware
  • Event loop concurrency model

Server Support: Netty (default), Tomcat, Jetty, Undertow, Servlet 3.1+

Feature Overview

1. Configuration → Details

Configure WebFlux using @EnableWebFlux and WebFluxConfigurer:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://example.com");
    }
}

Key Configuration Areas:

  • HTTP message codecs, formatters, validation
  • CORS, path matching, content type resolution
  • View resolvers, resource handlers
  • API versioning, blocking execution

2. WebClient → Details

Non-blocking HTTP client with fluent API:

WebClient client = WebClient.builder()
    .baseUrl("https://api.example.com")
    .defaultHeader("Authorization", "Bearer " + token)
    .build();

// GET request
Mono<User> user = client.get()
    .uri("/users/{id}", id)
    .retrieve()
    .bodyToMono(User.class);

// POST request with error handling
Mono<Response> response = client.post()
    .uri("/users")
    .bodyValue(newUser)
    .retrieve()
    .onStatus(HttpStatusCode::is4xxClientError,
        res -> Mono.error(new ClientException()))
    .bodyToMono(Response.class);

Features: Request/response filtering, timeout, retry, error handling, streaming

3. Functional Routing → Details

Composable, functional route definitions:

@Bean
public RouterFunction<ServerResponse> routes(UserHandler handler) {
    return route()
        .GET("/users/{id}", handler::getUser)
        .GET("/users", handler::listUsers)
        .POST("/users", handler::createUser)
        .filter((request, next) -> {
            // Logging, auth, etc.
            return next.handle(request);
        })
        .build();
}

Key Types: RouterFunction, HandlerFunction, ServerRequest, ServerResponse, RequestPredicate

4. Annotation Controllers → Details

Familiar Spring MVC-style controllers with reactive types:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return userService.findById(id);
    }

    @PostMapping
    public Mono<User> createUser(@RequestBody @Valid User user) {
        return userService.save(user);
    }
}

Method Arguments: @PathVariable, @RequestParam, @RequestBody, @RequestHeader, @CookieValue, ServerWebExchange, Principal, reactive types

Return Types: Mono<T>, Flux<T>, ResponseEntity, Rendering, void, plain objects

5. WebSocket → Details

Bidirectional real-time communication:

public class ChatHandler implements WebSocketHandler {
    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.send(
            session.receive()
                .map(msg -> session.textMessage("Echo: " + msg.getPayloadAsText()))
        );
    }
}

Features: Text/binary messages, ping/pong, handshake info, sub-protocols, multiple server implementations

6. Resource Handling → Details

Serve static resources with caching, versioning, transformation:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**")
        .addResourceLocations("classpath:/static/")
        .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
        .resourceChain(true)
        .addResolver(new VersionResourceResolver()
            .addContentVersionStrategy("/**"));
}

Features: Version strategies (content hash, fixed), gzip/brotli encoding, CSS link transformation, WebJars support

7. View Rendering → Details

Server-side template rendering:

@GetMapping("/users")
public Mono<Rendering> listUsers() {
    return userService.findAll()
        .collectList()
        .map(users -> Rendering.view("userList")
            .modelAttribute("users", users)
            .build());
}

Supported: FreeMarker, script templates (Nashorn, GraalVM JS), custom views

8. Content Negotiation & API Versioning → Details

Resolve content types and API versions:

@Override
public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
    builder.headerResolver()
        .parameterResolver("format")
        .mediaType("json", MediaType.APPLICATION_JSON)
        .mediaType("xml", MediaType.APPLICATION_XML);
}

@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
    configurer.addVersionResolver(new HeaderApiVersionResolver("API-Version"))
        .setVersionStrategy(new DefaultApiVersionStrategy()
            .addVersions("1.0", "2.0", "3.0")
            .addDeprecatedVersions("1.0"));
}

Version Resolution: Header, query parameter, path segment, media type parameter

9. Exception Handling → Details

Handle exceptions globally or per-controller:

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(NotFoundException.class)
    public Mono<ResponseEntity<ErrorResponse>> handleNotFound(NotFoundException ex) {
        return Mono.just(ResponseEntity.status(404)
            .body(new ErrorResponse("NOT_FOUND", ex.getMessage())));
    }
}

Approaches: @ExceptionHandler, ResponseEntityExceptionHandler, @ResponseStatus, functional onError(), DispatchExceptionHandler

Common Integration Patterns

REST API with Error Handling

@RestController
@RequestMapping("/api/users")
public class UserApiController {

    private final UserService userService;

    @GetMapping("/{id}")
    public Mono<ResponseEntity<User>> getUser(@PathVariable String id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }

    @PostMapping
    public Mono<ResponseEntity<User>> createUser(@RequestBody @Valid User user) {
        return userService.save(user)
            .map(saved -> ResponseEntity
                .created(URI.create("/api/users/" + saved.getId()))
                .body(saved));
    }

    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ErrorResponse> handleValidation(ValidationException ex) {
        return ResponseEntity.badRequest()
            .body(new ErrorResponse("VALIDATION_ERROR", ex.getMessage()));
    }
}

Streaming Data with Backpressure

@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Event>> streamEvents() {
    return eventService.subscribeToEvents()
        .map(event -> ServerSentEvent.builder(event)
            .id(event.getId())
            .event("message")
            .build())
        .onBackpressureBuffer(100);
}

File Upload with Multipart

@PostMapping("/upload")
public Mono<String> handleUpload(@RequestPart("file") FilePart file,
                                 @RequestPart("metadata") Metadata metadata) {
    return file.transferTo(Path.of("/uploads/" + file.filename()))
        .then(Mono.just("Upload successful"));
}

Authentication with WebFilter

@Bean
public WebFilter authenticationFilter() {
    return (exchange, chain) -> {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !isValid(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange)
            .contextWrite(Context.of("userId", extractUserId(token)));
    };
}

Core Types Reference

Request/Response Types

public interface ServerWebExchange {
    ServerHttpRequest getRequest();
    ServerHttpResponse getResponse();
    Mono<WebSession> getSession();
    Mono<? extends Principal> getPrincipal();
    Map<String, Object> getAttributes();
}

public class ServerHttpRequest extends HttpRequest {
    URI getURI();
    HttpHeaders getHeaders();
    MultiValueMap<String, HttpCookie> getCookies();
    Flux<DataBuffer> getBody();
}

public class ServerHttpResponse extends HttpResponse {
    void setStatusCode(HttpStatus status);
    HttpHeaders getHeaders();
    Mono<Void> writeWith(Publisher<? extends DataBuffer> body);
}

Handler Types

public interface WebHandler {
    Mono<Void> handle(ServerWebExchange exchange);
}

public class DispatcherHandler implements WebHandler {
    // Central dispatcher - delegates to HandlerMapping → HandlerAdapter
}

public interface HandlerMapping {
    Mono<Object> getHandler(ServerWebExchange exchange);
}

public interface HandlerAdapter {
    boolean supports(Object handler);
    Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler);
}

public class HandlerResult {
    Object getHandler();
    Object getReturnValue();
    MethodParameter getReturnType();
    BindingContext getBindingContext();
}

Configuration Types

@Target(ElementType.TYPE)
@Import(DelegatingWebFluxConfiguration.class)
public @interface EnableWebFlux { }

public interface WebFluxConfigurer {
    void configureHttpMessageCodecs(ServerCodecConfigurer configurer);
    void addFormatters(FormatterRegistry registry);
    void addCorsMappings(CorsRegistry registry);
    void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder);
    void configureApiVersioning(ApiVersionConfigurer configurer);
    void configurePathMatching(PathMatchConfigurer configurer);
    void configureViewResolvers(ViewResolverRegistry registry);
    void addResourceHandlers(ResourceHandlerRegistry registry);
    void configureBlockingExecution(BlockingExecutionConfigurer configurer);
}

Thread Safety & Concurrency

Thread Safety:

  • All WebFlux handlers execute on event loop threads (small thread pool)
  • Do NOT block event loop threads - use subscribeOn() or publishOn() for blocking operations
  • ServerWebExchange is NOT thread-safe - do not share across threads
  • Reactive operators are thread-safe and immutable

Blocking Operations:

// WRONG - blocks event loop
@GetMapping("/users")
public Mono<User> getUser() {
    User user = blockingJdbcCall(); // BAD!
    return Mono.just(user);
}

// CORRECT - offload blocking work
@GetMapping("/users")
public Mono<User> getUser() {
    return Mono.fromCallable(() -> blockingJdbcCall())
        .subscribeOn(Schedulers.boundedElastic());
}

Configure Blocking Scheduler:

@Override
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
    configurer.setExecutor(Schedulers.boundedElastic());
}

Error Handling Patterns

Reactive Error Operators:

  • onErrorReturn(fallbackValue) - return default value on error
  • onErrorResume(fallbackPublisher) - switch to fallback publisher
  • onErrorMap(mapper) - transform error
  • onErrorContinue((error, value) -> ...) - log and continue
  • retry(n) - retry on error
  • timeout(duration) - fail if no emission within duration
return webClient.get()
    .uri("/data")
    .retrieve()
    .bodyToMono(Data.class)
    .timeout(Duration.ofSeconds(5))
    .retry(3)
    .onErrorResume(TimeoutException.class, e ->
        Mono.just(new Data("cached")))
    .onErrorMap(WebClientException.class, e ->
        new ServiceException("Failed to fetch data", e));

Performance Considerations

Backpressure: WebFlux automatically handles backpressure - slow consumers signal upstream to slow down production

Memory:

  • Default max in-memory buffer: 256KB per request
  • Configure via ServerCodecConfigurer.defaultCodecs().maxInMemorySize()
  • Large payloads: Use streaming (Flux<DataBuffer>) instead of loading into memory

Connection Pooling:

HttpClient httpClient = HttpClient.create()
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
    .responseTimeout(Duration.ofSeconds(5))
    .option(ChannelOption.SO_KEEPALIVE, true);

WebClient client = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build();

Common Pitfalls

  1. Blocking Calls: Never block event loop threads - use subscribeOn(Schedulers.boundedElastic())
  2. Not Subscribing: Mono/Flux are lazy - nothing happens until subscribed (framework subscribes for controller return values)
  3. Dropping Errors: Always handle errors with onErrorResume() or let them propagate
  4. Memory Leaks: Release DataBuffer instances or use try-with-resources
  5. Incorrect Threading: Don't use ThreadLocal - use Reactor Context instead
  6. Blocking in Filters: WebFilter.filter() must return quickly - offload blocking work
  7. Large Request Bodies: Configure maxInMemorySize for large uploads
  8. Connection Leaks: Always consume response body completely or cancel

Additional Resources

  • Documentation: https://docs.spring.io/spring-framework/reference/web/webflux.html
  • Project Reactor: https://projectreactor.io/docs/core/release/reference/
  • Reactive Streams Spec: https://www.reactive-streams.org/