Backend Development 11 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring WebClient: Non‑Blocking HTTP Calls in Spring Boot

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>
JavaSpringHTTPreactiveSpringBootWebClient
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.