Spring Web Reactive | 2. WebClient | 2.1. Configuration

The easiest way to create a WebClient is to use one of the static factory methods.

  • WebClient.create()
  • WebClient.create(String baseUrl)

You can use WebClient.builder() with additional options.

  • uriBuilderFactory: A customized UriBuilderFactory to use as the base URL.
  • defaultUriVariables: Default values used when expanding URI templates.
  • defaultHeader: Headers used for every request.
  • defaultCookie: Cookies used for every request.
  • defaultRequest: Customize every request with a Consumer.
  • filter: Client filters for every request.
  • exchangeStrategies: Customization of HTTP message readers/writers.
  • clientConnector: Settings for the HTTP client library.

For example:

Java

WebClient client = WebClient.builder()
        .codecs(configurer -> ... )
        .build();

Kotlin

val webClient = WebClient.builder()
        .codecs { configurer -> ... }
        .build()

Once built, WebClient is immutable. However, you can clone it and create a modified copy as follows.

Java

WebClient client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build();

WebClient client2 = client1.mutate()
        .filter(filterC).filter(filterD).build();

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD

Kotlin

val client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build()

val client2 = client1.mutate()
        .filter(filterC).filter(filterD).build()

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD

2.1.1. MaxInMemorySize

Codecs have limits for buffering data in memory in order to address memory issues in applications. By default, this is set to 256KB. If that is not enough, the following error occurs.

org.springframework.core.io.buffer.DataBufferLimitException : Exceeded limit on max bytes to buffer

To change the default codec limit, configure the following.

Java

WebClient webClient = WebClient.builder()
        .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
        .build();

Kotlin

val webClient = WebClient.builder()
        .codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) }
        .build()

2.1.2. Reactor Netty

To customize Reactor Netty settings, provide a preconfigured HttpClient.

Java

HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);

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

Kotlin

val httpClient = HttpClient.create().secure { ... }

val webClient = WebClient.builder()
    .clientConnector(ReactorClientHttpConnector(httpClient))
    .build()

Resources

By default, HttpClient uses global Reactor Netty resources in reactor.netty.http.HttpResources, including event loop threads and the connection pool. This is the recommended mode because fixed, shared resources are preferred for concurrent execution of event loops. In this mode, global resources remain active until the process exits.

If the timing is aligned with the server process, you generally do not need to shut them down explicitly. However, if a server can be started and stopped inside the process, for example a Spring MVC application deployed as a WAR, you can declare a Spring-managed Bean of type ReactorResourceFactory with globalResources=true, the default, and confirm that Reactor Netty is global. As shown in the following example, resources are shut down when the Spring ApplicationContext closes.

Java

@Bean
public ReactorResourceFactory reactorResourceFactory() {
    return new ReactorResourceFactory();
}

Kotlin

@Bean
fun reactorResourceFactory() = ReactorResourceFactory()

You can also choose not to use global Reactor Netty resources. However, in this mode you are responsible for ensuring that all Reactor Netty client and server instances use shared resources, as shown in the following example.

Java

@Bean
public ReactorResourceFactory resourceFactory() {
    ReactorResourceFactory factory = new ReactorResourceFactory();
    factory.setUseGlobalResources(false);   // (1)
    return factory;
}

@Bean
public WebClient webClient() {

    Function<HttpClient, HttpClient> mapper = client -> {
        // Further customizations...
    };

    ClientHttpConnector connector =
            new ReactorClientHttpConnector(resourceFactory(), mapper);   // (2)

    return WebClient.builder().clientConnector(connector).build();   // (3)
}

Kotlin

@Bean
fun resourceFactory() = ReactorResourceFactory().apply {
    isUseGlobalResources = false   // (1)
}

@Bean
fun webClient(): WebClient {

    val mapper: (HttpClient) -> HttpClient = {
        // Further customizations...
    }

    val connector = ReactorClientHttpConnector(resourceFactory(), mapper)   // (2)

    return WebClient.builder().clientConnector(connector).build()   // (3)
}
  • (1) Create resources independent of global resources.
  • (2) Use the ReactorClientHttpConnector constructor with the resource factory.
  • (3) Connect the connector to WebClient.Builder.

Timeouts

To configure the connection timeout:

Java

import io.netty.channel.ChannelOption;

HttpClient httpClient = HttpClient.create()
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);

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

Kotlin

import io.netty.channel.ChannelOption

val httpClient = HttpClient.create()
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);

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

To configure read or write timeouts:

Java

import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;

HttpClient httpClient = HttpClient.create()
        .doOnConnected(conn -> conn
                .addHandlerLast(new ReadTimeoutHandler(10))
                .addHandlerLast(new WriteTimeoutHandler(10)));

// Create WebClient...

Kotlin

import io.netty.handler.timeout.ReadTimeoutHandler
import io.netty.handler.timeout.WriteTimeoutHandler

val httpClient = HttpClient.create()
        .doOnConnected { conn -> conn
                .addHandlerLast(new ReadTimeoutHandler(10))
                .addHandlerLast(new WriteTimeoutHandler(10))
        }

// Create WebClient...

To set the response timeout for all requests:

Java

HttpClient httpClient = HttpClient.create()
        .responseTimeout(Duration.ofSeconds(2));

// Create WebClient...

Kotlin

val httpClient = HttpClient.create()
        .responseTimeout(Duration.ofSeconds(2));

// Create WebClient...

To set the response timeout for a specific request:

Java

WebClient.create().get()
        .uri("https://example.org/path")
        .httpRequest(httpRequest -> {
            HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
            reactorRequest.responseTimeout(Duration.ofSeconds(2));
        })
        .retrieve()
        .bodyToMono(String.class);

Kotlin

WebClient.create().get()
        .uri("https://example.org/path")
        .httpRequest { httpRequest: ClientHttpRequest ->
            val reactorRequest = httpRequest.getNativeRequest<HttpClientRequest>()
            reactorRequest.responseTimeout(Duration.ofSeconds(2))
        }
        .retrieve()
        .bodyToMono(String::class.java)

2.1.3. Jetty

The following example shows how to customize Jetty HttpClient settings.

Java

HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);

WebClient webClient = WebClient.builder()
        .clientConnector(new JettyClientHttpConnector(httpClient))
        .build();

Kotlin

val httpClient = HttpClient()
httpClient.cookieStore = ...

val webClient = WebClient.builder()
        .clientConnector(new JettyClientHttpConnector(httpClient))
        .build();

By default, HttpClient creates its own resources (Executor, Scheduler, ByteBufferPool) and keeps them active until the process exits or stop() is called.

As shown in the following example, by declaring JettyResourceFactory as a Spring-managed Bean, you can share resources across multiple instances of Jetty clients and servers and reliably shut down resources when the Spring ApplicationContext closes.

Java

@Bean
public JettyResourceFactory resourceFactory() {
    return new JettyResourceFactory();
}

@Bean
public WebClient webClient() {

    HttpClient httpClient = new HttpClient();
    // Further customizations...

    ClientHttpConnector connector =
            new JettyClientHttpConnector(httpClient, resourceFactory());   (1)

    return WebClient.builder().clientConnector(connector).build();   (2)
}

Kotlin

@Bean
fun resourceFactory() = JettyResourceFactory()

@Bean
fun webClient(): WebClient {

    val httpClient = HttpClient()
    // Further customizations...

    val connector = JettyClientHttpConnector(httpClient, resourceFactory())   (1)

    return WebClient.builder().clientConnector(connector).build()   (2)
}
  • (1) Use the JettyClientHttpConnector constructor with the resource factory.
  • (2) Connect the connector to WebClient.Builder.

2.1.4. HttpComponents

The following example shows how to customize Apache HttpComponents HttpClient settings.

Java

HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(...);
CloseableHttpAsyncClient client = clientBuilder.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);

WebClient webClient = WebClient.builder().clientConnector(connector).build();

Kotlin

val client = HttpAsyncClients.custom().apply {
    setDefaultRequestConfig(...)
}.build()
val connector = HttpComponentsClientHttpConnector(client)
val webClient = WebClient.builder().clientConnector(connector).build()