I'm using Spring Boot 3.0.4 with Java 17. The Spring WebClient
documentation says to use the injected WebClient.Builder
:
Spring Boot creates and pre-configures a
WebClient.Builder
for you. It is strongly advised to inject it in your components and use it to createWebClient
instances. Spring Boot is configuring that builder to share HTTP resources, reflect codecs setup in the same fashion as the server ones …, and more.
The documentation also says:
Spring Boot will auto-detect which ClientHttpConnector to use to drive WebClient, depending on the libraries available on the application classpath. For now, Reactor Netty, Jetty ReactiveStream client, Apache HttpClient, and the JDK’s HttpClient are supported.
This is a bit unclear to me. I had read in books and articles that Spring Boot will use Netty automatically for WebClient
. But does this mean that without further configuration, the latest Spring Boot will use the JDK HttpClient
? Note that I have included spring-boot-starter-web
and spring-boot-starter-webflux
in my project, but nothing specifically relating to Netty.
Furthermore the Spring Reactor documentation tells me that I can configure a connection timeout like this if I am using the Netty runtime:
import io.netty.channel.ChannelOption;
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
But what is the timeout default already, if I don't add this code? And if I don't like the default and want to use this code, how do I override the default WebClient.Builder
(mentioned above) without building one from scratch (and possibly negating all the other benefits)?
So let me summarize my doubts, based upon all this slightly ambiguous documentation:
spring-boot-starter-web
and spring-boot-starter-webflux
, is Spring WebClient
using Netty or JDK HttpClient
. (If it's using Netty by default, why does the documentation even mention JDK HttpClient
? How would I force JDK HttpClient
?)WebClient.Builder
, and where is this documented (or how can I find this out in the source code)?WebClient.Builder
which is injected into the Spring context automatically?HoaPhan
has already pointed out the code that checks for the presence of different HttpClient
classes in the classpath. Based on those checks, the initialization of a ClientHttpConnector
object happens in this method. As we can see in the linked code(included below), a JdkClientHttpConnector
gets initialized if none of the other libraries are present in the classpath
private ClientHttpConnector initConnector() {
if (reactorNettyClientPresent) {
return new ReactorClientHttpConnector();
}
else if (reactorNetty2ClientPresent) {
return new ReactorNetty2ClientHttpConnector();
}
else if (jettyClientPresent) {
return new JettyClientHttpConnector();
}
else if (httpComponentsClientPresent) {
return new HttpComponentsClientHttpConnector();
}
else {
return new JdkClientHttpConnector();
}
}
Looks like netty gets added as a transient dependency when using spring-boot-starter-webflux
. So to force the code to use the JDK HttpClient, you can do either what HoaPhan
suggested or exclude netty dependencies from the classpath, which on gradle would look something like below:
implementation(group: 'org.springframework.boot', name: 'spring-boot-starter-webflux') {
exclude group: 'io.projectreactor.netty'
}
With the above exclusion and with none of the other HttpClient
libraries present in the classpath, a WebClient
bean like below should use the JDK HttpClient:
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder.build();
}
The default connect timeout, if using the netty client, is 30 seconds. The timeouts are documented here
Overriding the timeout in the preconfigured WebClient.Builder
bean can be done using the same code you have included in the question, substituting WebClient.builder()
with the injected WebClient.Builder
bean. Something like below:
package io.github.devatherock.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import io.netty.channel.ChannelOption;
import reactor.netty.http.client.HttpClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
return builder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
UPDATE:
To create WebClient
objects with different configurations from the single pre-configured WebClient.Builder
bean, we'll need to clone the builder bean first
@Bean
public WebClient accountsClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
return builder.clone()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
@Bean
public WebClient payrollClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
return builder.clone()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
UPDATE 2:
To set timeouts and other customizations to the pre-configured WebClient.Builder
bean, the simplest way would be to provide a custom WebClientCustomizer
bean, like HoaPhan
pointed out, as spring-boot
applies all customizers to the WebClient.Builder
bean when it is created. Then the customized WebClient.Builder
bean can used to create as many WebClient
objects as required. Sample below:
package io.github.devatherock.config;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import io.netty.channel.ChannelOption;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import reactor.netty.http.client.HttpClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClientCustomizer timeoutCustomizer() {
return builder -> {
HttpClient httpClient = HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
builder.clientConnector(new ReactorClientHttpConnector(httpClient));
};
}
@Bean
public GoogleService googleService(WebClient.Builder builder) {
WebClient googleClient = builder
.baseUrl("https://www.google.com")
.build();
return new GoogleService(googleClient);
}
@Bean
public BingService bingService(WebClient.Builder builder) {
WebClient bingClient = builder
.baseUrl("https://www.bing.com")
.build();
return new BingService(bingClient);
}
}