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

webclient.mddocs/

WebClient - Non-blocking HTTP Client

WebClient is Spring WebFlux's non-blocking, reactive HTTP client for making requests to external APIs. It provides a fluent API for building requests, handling responses, and applying filters for cross-cutting concerns like authentication, logging, and error handling.

Capabilities

Create WebClient

Factory methods for creating WebClient instances.

public interface WebClient {
    // Create a WebClient with default settings
    static WebClient create() { ... }

    // Create a WebClient with a base URL
    static WebClient create(String baseUrl) { ... }

    // Create a WebClient builder for customization
    static WebClient.Builder builder() { ... }

    // Create a mutated copy of this WebClient
    Builder mutate();
}

Usage:

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

// Simple creation
WebClient client = WebClient.create("https://api.example.com");

// Using builder for customization
WebClient client = WebClient.builder()
    .baseUrl("https://api.example.com")
    .defaultHeader("User-Agent", "My App")
    .defaultCookie("session", "...")
    .build();

Configure WebClient

Builder interface for configuring WebClient instances.

interface Builder {
    // Set base URL for requests
    Builder baseUrl(String baseUrl);

    // Set default URI variables
    Builder defaultUriVariables(Map<String, ?> defaultUriVariables);

    // Set default headers for all requests
    Builder defaultHeader(String header, String... values);
    Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer);

    // Set default cookies for all requests
    Builder defaultCookie(String cookie, String... values);
    Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);

    // Add default headers/cookies from Consumer
    Builder defaultRequest(Consumer<WebClient.RequestHeadersSpec<?>> defaultRequest);

    // Add filter for request/response processing
    Builder filter(ExchangeFilterFunction filter);
    Builder filters(Consumer<List<ExchangeFilterFunction>> filtersConsumer);

    // Configure HTTP client connector
    Builder clientConnector(ClientHttpConnector connector);

    // Configure message readers/writers
    Builder codecs(Consumer<ClientCodecConfigurer> configurer);

    // Configure exchange strategies
    Builder exchangeStrategies(ExchangeStrategies strategies);

    // Configure exchange function
    Builder exchangeFunction(ExchangeFunction exchangeFunction);

    // Apply initialization function
    Builder apply(Consumer<Builder> builderConsumer);

    // Clone this builder
    Builder clone();

    // Build the WebClient
    WebClient build();
}

Usage:

WebClient client = WebClient.builder()
    .baseUrl("https://api.example.com")
    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token)
    .filter(ExchangeFilterFunctions.basicAuthentication("user", "password"))
    .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
    .build();

Make HTTP Requests

Methods for initiating HTTP requests with different HTTP methods.

public interface WebClient {
    // HTTP GET request
    RequestHeadersUriSpec<?> get();

    // HTTP POST request
    RequestBodyUriSpec post();

    // HTTP PUT request
    RequestBodyUriSpec put();

    // HTTP PATCH request
    RequestBodyUriSpec patch();

    // HTTP DELETE request
    RequestHeadersUriSpec<?> delete();

    // HTTP HEAD request
    RequestHeadersUriSpec<?> head();

    // HTTP OPTIONS request
    RequestHeadersUriSpec<?> options();

    // HTTP request with custom method
    RequestBodyUriSpec method(HttpMethod method);
}

Usage:

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

// POST request
Mono<User> created = client.post()
    .uri("/users")
    .bodyValue(newUser)
    .retrieve()
    .bodyToMono(User.class);

// PUT request
Mono<Void> updated = client.put()
    .uri("/users/{id}", userId)
    .bodyValue(updatedUser)
    .retrieve()
    .bodyToMono(Void.class);

// DELETE request
Mono<Void> deleted = client.delete()
    .uri("/users/{id}", userId)
    .retrieve()
    .bodyToMono(Void.class);

Specify Request URI

Interface for specifying the request URI.

interface UriSpec<S extends RequestHeadersSpec<?>> {
    // URI as String
    S uri(String uri, Object... uriVariables);
    S uri(String uri, Map<String, ?> uriVariables);

    // URI using UriBuilder
    S uri(Function<UriBuilder, URI> uriFunction);

    // URI as java.net.URI
    S uri(URI uri);
}
interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>> extends UriSpec<S>, RequestHeadersSpec<S> { }
interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec<RequestBodySpec> { }

Usage:

// URI with path variables
client.get()
    .uri("/users/{id}/orders/{orderId}", userId, orderId)
    .retrieve();

// URI with query parameters using UriBuilder
client.get()
    .uri(uriBuilder -> uriBuilder
        .path("/users")
        .queryParam("page", 1)
        .queryParam("size", 20)
        .build())
    .retrieve();

Configure Request Headers

Interface for configuring request headers.

interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> extends RequestHeadersUriSpec<S> {
    // Set Accept header
    S accept(MediaType... acceptableMediaTypes);

    // Set Accept-Charset header
    S acceptCharset(Charset... acceptableCharsets);

    // Set If-Modified-Since header
    S ifModifiedSince(ZonedDateTime ifModifiedSince);

    // Set If-None-Match header
    S ifNoneMatch(String... ifNoneMatches);

    // Set custom header
    S header(String headerName, String... headerValues);

    // Set headers using Consumer
    S headers(Consumer<HttpHeaders> headersConsumer);

    // Set request attribute
    S attribute(String name, Object value);

    // Set attributes using Consumer
    S attributes(Consumer<Map<String, Object>> attributesConsumer);

    // Set cookie
    S cookie(String name, String value);

    // Set cookies using Consumer
    S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);

    // Retrieve response
    ResponseSpec retrieve();

    // Exchange for full control over request and response
    Mono<ClientResponse> exchange();

    // Exchange to Mono
    <V> Mono<V> exchangeToMono(Function<ClientResponse, ? extends Mono<V>> responseHandler);

    // Exchange to Flux
    <V> Flux<V> exchangeToFlux(Function<ClientResponse, ? extends Flux<V>> responseHandler);
}

Usage:

client.get()
    .uri("/data")
    .accept(MediaType.APPLICATION_JSON)
    .header("X-Custom-Header", "value")
    .headers(headers -> {
        headers.setBearerAuth(token);
        headers.set("X-Request-ID", requestId);
    })
    .cookie("session", sessionId)
    .retrieve()
    .bodyToMono(String.class);

Configure Request Body

Interface for configuring request body for POST, PUT, PATCH requests.

interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
    // Set Content-Length header
    RequestBodySpec contentLength(long contentLength);

    // Set Content-Type header
    RequestBodySpec contentType(MediaType contentType);

    // Set request body from Object
    RequestHeadersSpec<?> bodyValue(Object body);

    // Set request body from Publisher (Mono or Flux)
    <T, P extends Publisher<T>> RequestHeadersSpec<?> body(P publisher, Class<T> elementClass);
    <T, P extends Publisher<T>> RequestHeadersSpec<?> body(P publisher, ParameterizedTypeReference<T> elementTypeRef);

    // Set request body using BodyInserter
    RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
}

Usage:

// Simple body value
client.post()
    .uri("/users")
    .contentType(MediaType.APPLICATION_JSON)
    .bodyValue(user)
    .retrieve();

// Publisher body (Mono)
Mono<User> userMono = ...;
client.post()
    .uri("/users")
    .body(userMono, User.class)
    .retrieve();

// Publisher body (Flux)
Flux<User> usersFlux = ...;
client.post()
    .uri("/users/batch")
    .body(usersFlux, User.class)
    .retrieve();

// Multipart form data
client.post()
    .uri("/upload")
    .body(BodyInserters.fromMultipartData("file", fileResource)
        .with("description", "My file"))
    .retrieve();

Handle Response

Interface for handling responses and extracting body.

interface ResponseSpec {
    // Handle specific status codes
    ResponseSpec onStatus(Predicate<HttpStatusCode> statusPredicate, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction);

    // Extract body as Mono
    <T> Mono<T> bodyToMono(Class<T> elementClass);
    <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> elementTypeRef);

    // Extract body as Flux
    <T> Flux<T> bodyToFlux(Class<T> elementClass);
    <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> elementTypeRef);

    // Convert to bodiless entity
    Mono<ResponseEntity<Void>> toBodilessEntity();

    // Convert to ResponseEntity with body
    <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType);
    <T> Mono<ResponseEntity<T>> toEntity(ParameterizedTypeReference<T> bodyTypeReference);

    // Convert to ResponseEntity with List body
    <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType);
    <T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> elementTypeRef);

    // Convert to ResponseEntity with Flux body
    <T> ResponseEntity<Flux<T>> toEntityFlux(Class<T> elementType);
    <T> ResponseEntity<Flux<T>> toEntityFlux(ParameterizedTypeReference<T> elementTypeRef);
}

Usage:

// Extract body as Mono
Mono<User> user = client.get()
    .uri("/users/{id}", userId)
    .retrieve()
    .bodyToMono(User.class);

// Extract body as Flux
Flux<User> users = client.get()
    .uri("/users")
    .retrieve()
    .bodyToFlux(User.class);

// Handle specific errors
Mono<User> user = client.get()
    .uri("/users/{id}", userId)
    .retrieve()
    .onStatus(HttpStatusCode::is4xxClientError,
        response -> Mono.error(new NotFoundException("User not found")))
    .onStatus(HttpStatusCode::is5xxServerError,
        response -> Mono.error(new ServerException("Server error")))
    .bodyToMono(User.class);

// Get ResponseEntity with body
Mono<ResponseEntity<User>> entity = client.get()
    .uri("/users/{id}", userId)
    .retrieve()
    .toEntity(User.class);

Exchange Filters

Apply filters for cross-cutting concerns like authentication, logging, and error handling.

@FunctionalInterface
public interface ExchangeFilterFunction {
    Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next);

    // Chain filters
    default ExchangeFilterFunction andThen(ExchangeFilterFunction afterFilter) { ... }

    // Apply filter to ExchangeFunction
    default ExchangeFunction apply(ExchangeFunction exchange) { ... }
}

Static factory methods for common filters:

public class ExchangeFilterFunctions {
    // Basic authentication
    public static ExchangeFilterFunction basicAuthentication(String username, String password) { ... }

    // Handle status codes
    public static ExchangeFilterFunction statusError(Predicate<HttpStatusCode> statusPredicate,
        Function<ClientResponse, ? extends Throwable> exceptionFunction) { ... }

    // Limit response size
    public static ExchangeFilterFunction limitResponseSize(long maxByteCount) { ... }
}

Custom filter example:

ExchangeFilterFunction loggingFilter = (request, next) -> {
    System.out.println("Request: " + request.method() + " " + request.url());
    return next.exchange(request)
        .doOnNext(response -> System.out.println("Response: " + response.statusCode()));
};

WebClient client = WebClient.builder()
    .filter(loggingFilter)
    .filter(ExchangeFilterFunctions.basicAuthentication("user", "password"))
    .build();

Exchange Strategies

Configure message readers and writers for request/response body conversion.

public interface ExchangeStrategies {
    // Get message readers
    List<HttpMessageReader<?>> messageReaders();

    // Get message writers
    List<HttpMessageWriter<?>> messageWriters();

    // Create default strategies
    static ExchangeStrategies withDefaults() { ... }

    // Create builder
    static Builder builder() { ... }

    // Mutate existing strategies
    Builder mutate();

    interface Builder {
        // Configure default codecs
        Builder codecs(Consumer<ClientCodecConfigurer> configurer);

        // Build strategies
        ExchangeStrategies build();
    }
}

Usage:

ExchangeStrategies strategies = ExchangeStrategies.builder()
    .codecs(configurer -> {
        configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024);
        configurer.defaultCodecs().enableLoggingRequestDetails(true);
    })
    .build();

WebClient client = WebClient.builder()
    .exchangeStrategies(strategies)
    .build();

Client Request and Response

Low-level request and response types for use with exchange() method.

public interface ClientRequest {
    HttpMethod method();
    URI url();
    HttpHeaders headers();
    MultiValueMap<String, String> cookies();
    BodyInserter<?, ? super ClientHttpRequest> body();
    Map<String, Object> attributes();
    Consumer<ClientHttpRequest> httpRequest();

    static Builder create(HttpMethod method, URI url) { ... }
    Builder mutate();

    interface Builder {
        Builder method(HttpMethod method);
        Builder url(URI url);
        Builder header(String headerName, String... headerValues);
        Builder headers(Consumer<HttpHeaders> headersConsumer);
        Builder cookie(String name, String value);
        Builder cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
        Builder body(BodyInserter<?, ? super ClientHttpRequest> inserter);
        Builder attribute(String name, Object value);
        Builder attributes(Consumer<Map<String, Object>> attributesConsumer);
        Builder httpRequest(Consumer<ClientHttpRequest> requestConsumer);
        ClientRequest build();
    }
}
public interface ClientResponse {
    HttpStatusCode statusCode();
    int rawStatusCode();
    HttpHeaders headers();
    MultiValueMap<String, ResponseCookie> cookies();

    // Extract body
    <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor);
    <T> Mono<T> bodyToMono(Class<? extends T> elementClass);
    <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> elementTypeRef);
    <T> Flux<T> bodyToFlux(Class<? extends T> elementClass);
    <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> elementTypeRef);

    // Convert to entity
    Mono<ResponseEntity<Void>> toBodilessEntity();
    <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType);
    <T> Mono<ResponseEntity<T>> toEntity(ParameterizedTypeReference<T> bodyTypeRef);
    <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType);
    <T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> elementTypeRef);

    // Release body without consuming
    Mono<Void> releaseBody();

    // Create error from response status
    <T> Mono<T> createError();

    ExchangeStrategies strategies();
    Builder mutate();

    interface Headers {
        OptionalLong contentLength();
        Optional<MediaType> contentType();
        List<String> header(String headerName);
        HttpHeaders asHttpHeaders();
    }

    interface Builder { ... }
}

Usage with exchange:

Mono<String> result = client.get()
    .uri("/data")
    .exchangeToMono(response -> {
        if (response.statusCode().is2xxSuccessful()) {
            return response.bodyToMono(String.class);
        } else {
            return response.createError();
        }
    });

Types

Exceptions

// Base exception for WebClient errors
public class WebClientException extends NestedRuntimeException {
    public WebClientException(String msg) { ... }
    public WebClientException(String msg, Throwable cause) { ... }
}
// Exception for I/O errors during request
public class WebClientRequestException extends WebClientException {
    public WebClientRequestException(Throwable ex, HttpMethod method, URI uri, HttpHeaders headers) { ... }
    public HttpMethod getMethod() { ... }
    public URI getUri() { ... }
    public HttpHeaders getHeaders() { ... }
}
// Exception for HTTP error status codes
public class WebClientResponseException extends WebClientException {
    public WebClientResponseException(String message, int statusCode, String statusText,
        HttpHeaders headers, byte[] responseBody, Charset responseCharset) { ... }

    public HttpStatusCode getStatusCode() { ... }
    public int getRawStatusCode() { ... }
    public String getStatusText() { ... }
    public HttpHeaders getHeaders() { ... }
    public byte[] getResponseBodyAsByteArray() { ... }
    public String getResponseBodyAsString() { ... }
    public <T> T getResponseBodyAs(Class<T> targetType) { ... }

    // Specific status code exceptions
    public static class BadRequest extends WebClientResponseException { ... }
    public static class Unauthorized extends WebClientResponseException { ... }
    public static class Forbidden extends WebClientResponseException { ... }
    public static class NotFound extends WebClientResponseException { ... }
    public static class MethodNotAllowed extends WebClientResponseException { ... }
    public static class NotAcceptable extends WebClientResponseException { ... }
    public static class Conflict extends WebClientResponseException { ... }
    public static class Gone extends WebClientResponseException { ... }
    public static class UnsupportedMediaType extends WebClientResponseException { ... }
    public static class TooManyRequests extends WebClientResponseException { ... }
    public static class InternalServerError extends WebClientResponseException { ... }
    public static class BadGateway extends WebClientResponseException { ... }
    public static class ServiceUnavailable extends WebClientResponseException { ... }
    public static class GatewayTimeout extends WebClientResponseException { ... }
}
// Exception for unknown HTTP status codes
public class UnknownHttpStatusCodeException extends WebClientResponseException {
    public UnknownHttpStatusCodeException(int statusCode, HttpHeaders headers, byte[] responseBody, Charset charset) { ... }
}