Mastering Spring WebClient: Non‑Blocking HTTP Calls in Spring Boot
This guide introduces Spring WebClient, a reactive, non‑blocking HTTP client for Spring Boot, compares it with RestTemplate, explains configuration options, demonstrates usage patterns, error handling, memory limits, Reactor Netty customization, request bodies, form data, and filter registration, providing practical code examples throughout.
Environment: SpringBoot 2.6.12
1. Overview
Spring WebClient is a reactive, non‑blocking HTTP client introduced in Spring 5, providing a modern, efficient and flexible way to interact with remote services.
Key characteristics:
Non‑blocking: uses Reactor for asynchronous processing, allowing multiple concurrent requests without blocking threads.
Integration: easily integrates with Spring MVC, Spring WebFlux and supports various HTTP message converters.
Customisation: offers options such as timeout, connection pool, retry policies, etc.
Error handling: supports graceful error handling via exception handlers.
Ease of use: fluent API or lambda expressions simplify request construction.
In short, Spring WebClient is a powerful HTTP client for reactive applications, offering efficient, scalable, non‑blocking communication and seamless Spring integration.
2. WebClient vs RestTemplate
Both are Spring tools for sending HTTP requests, but they differ fundamentally.
Blocking vs Non‑blocking
RestTemplate uses the Servlet API, allocating a thread per request and blocking until a response arrives.
WebClient leverages Spring Reactive to handle requests asynchronously without blocking threads.
Performance
RestTemplate’s blocking model can degrade performance under high concurrency due to thread overhead.
WebClient’s non‑blocking model uses system resources more efficiently, reducing CPU and memory usage.
Use cases
RestTemplate remains the default for most Spring applications, simple and familiar.
WebClient, introduced in Spring 5, is suited for reactive applications and high‑concurrency scenarios.
3. WebClient Configuration
Typical creation methods:
WebClient.create()
WebClient.create(String baseUrl)
Builder provides additional options such as uriBuilderFactory , defaultHeader , defaultCookie , filter , exchangeStrategies , clientConnector , etc.
4. Simple Usage Examples
Example 1 – basic client:
<code>WebClient client = WebClient.builder()
.codecs(configurer -> ...)
.build();</code>Example 2 – cloning and mutating a client:
<code>WebClient client1 = WebClient.builder()
.filter(filterA).filter(filterB).build();
// create a copy
WebClient client2 = client1.mutate()
.filter(filterC).filter(filterD).build();</code>5. Maximum In‑Memory Size
Default codec buffer limit is 256 KB. Exceeding it throws DataBufferLimitException . Increase the limit as follows:
<code>WebClient webClient = WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.build();</code>6. Reactor Netty
Reactor Netty is the default HTTP client. Custom settings such as connection and read/write timeouts can be configured:
<code>HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.doOnConnected(con -> {
con.addHandlerFirst(new ReadTimeoutHandler(2, TimeUnit.SECONDS));
con.addHandlerLast(new WriteTimeoutHandler(10));
});
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
WebClient client = WebClient.builder().clientConnector(connector).build();</code>7. Retrieving Response Data
<code>WebClient client = WebClient.create("https://example.org");
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Person.class);</code>Or directly obtain the body:
<code>Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);</code>Handle error statuses with onStatus :
<code>Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, resp -> ...)
.onStatus(HttpStatus::is5xxServerError, resp -> ...)
.bodyToMono(Person.class);</code>8. Exchange Operations
For fine‑grained control, use exchangeToMono or exchangeToFlux :
<code>@GetMapping("/removeInvoke3")
public Mono<R> remoteInvoke3() {
return wc.get()
.uri("http://localhost:9000/users/get?id={id}", new Random().nextInt(1000000))
.exchangeToMono(clientResponse -> {
if (clientResponse.statusCode().equals(HttpStatus.OK)) {
return clientResponse.bodyToMono(Users.class);
} else {
return clientResponse.createException().flatMap(Mono::error);
}
})
.log()
.flatMap(user -> Mono.just(R.success(user)))
.retry(3)
.onErrorResume(ex -> Mono.just(R.failure(ex.getMessage())));
}</code>9. Request Body
Encode any reactive type, e.g., a Mono :
<code>Mono<Person> personMono = ...;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);</code>Or use bodyValue for a concrete object:
<code>Person person = ...;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(person)
.retrieve()
.bodyToMono(Void.class);</code>10. Form Data
Send form data with a MultiValueMap which is automatically written as application/x-www-form-urlencoded :
<code>MultiValueMap<String, String> formData = ...;
Mono<Void> result = client.post()
.uri("/path", id)
.bodyValue(formData)
.retrieve()
.bodyToMono(Void.class);</code>Convenient static import:
<code>import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);</code>11. Filters
Register client filters ( ExchangeFilterFunction ) to intercept and modify requests:
<code>WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();</code>Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.