In modern microservices architecture, services frequently need to communicate with each other or with external APIs over HTTP. Traditionally, developers relied on RestTemplate for such calls, but with the rise of reactive programming and the need for better scalability, Spring introduced WebClient as part of the Spring WebFlux module.
WebClient is a non-blocking, reactive HTTP client that supports both synchronous and asynchronous communication. It is lightweight, efficient, and designed to handle high-concurrency scenarios, making it the preferred choice for building resilient and scalable microservices.
πΉ Features of WebClient in Spring Boot
Non-blocking & Reactive β Built on Project Reactor, it supports asynchronous and non-blocking communication, improving scalability.
Supports All HTTP Methods β Works with GET, POST, PUT, DELETE, PATCH, and more.
Flexible Response Handling β Convert responses into Mono (single object) or Flux (multiple objects/streams).
Customizable Requests β Easy to add headers, query parameters, cookies, and authentication tokens.
Error Handling β Provides built-in error handling for 4xx/5xx and customizable handling with .onStatus().
Lightweight & Future-Proof β Official replacement for RestTemplate, designed for cloud-native applications.
πΉReactive Types β Mono and Flux
When using WebClient, the HTTP response is not returned immediately like a traditional call. Instead, it is wrapped in a reactive type which represents a pipeline of data that will be available in the future.
To actually read the response body, we use .retrieve(), which triggers the HTTP call and prepares the response for conversion into Mono or Flux.
Spring WebFlux uses Project Reactor, which provides two main reactive types:
Mono β Single or No Result
Represents 0 or 1 item.
Use when the API returns a single object or might be empty.
Examples: fetching a single customer, saving an entity, deleting by ID.
Flux β Multiple or Stream of Results
Represents 0 to N items.
Use when the API returns a list of items or a continuous stream.
Examples: fetching all customers, streaming logs, Server-Sent Events.
πΉ WebClient Configuration
- Add Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
- Defining as a Spring Bean for reuse.
@Configuration
public class WebClientConfig {
@Bean
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
- Inject Webclient
@Service
public class BookingService {
private final WebClient webClient;
@Autowired
public BookingService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://localhost:8081").build();
}
}
πΉ GET API Example
GET API is use to fetch either a collection of resources or a singular resource
β’ HTTP GET /hotel: List of Hotel as Flux
public List<HotelResponse> getAllHotels() {
return webClient
.get()
.uri("/hotel")
.retrieve()
.bodyToFlux(HotelResponse.class)
.collectList()
.block();
}
β’ HTTP GET /hotel/{id}: single hotel by id as Mono
HotelResponse hotel = webClient
.get()
.uri("hotel/{id}", booking.getHotelId())
.retrieve()
.bodyToMono(HotelResponse.class)
.block();
πΉ PUT API Example
PUT API is commonly used for updating a resource.
webClient.put()
.uri("/hotel/{id}/update-rooms?bookedRooms={count}",
booking.getHotelId(), booking.getNoOfRoomsBooked())
.retrieve()
.bodyToMono(Void.class)
.block();
πΉ Exception Handling in WebClient
When working with microservices, one service often calls another over HTTP. But what happens if the target service is down, or returns an error (like 404 or 500)? Without proper handling, users might see raw stack traces or cryptic messages.
With Spring WebClient, we can gracefully handle these errors using .onStatus() and @ControllerAdvice.
Error Handling in WebClient
.onStatus() : handle remote service failures.
HotelResponse hotel = webClient
.get()
.uri("hotel/{id}", booking.getHotelId())
.retrieve()
.onStatus(
status -> status.is4xxClientError(),
response -> response.bodyToMono(String.class)
.map(msg -> new RuntimeException("Hotel not found: " + msg))
)
.onStatus(
status -> status.is5xxServerError(),
response -> response.bodyToMono(String.class)
.map(msg -> new RuntimeException("Hotel Service error: " + msg))
)
.bodyToMono(HotelResponse.class)
.block();
Global Exception Handling with @ControllerAdvice
At the application level, we can use @RestControllerAdvice to catch exceptions globally and return clean JSON responses instead of stack traces.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException ex) {
Map<String, Object> error = new HashMap<>();
error.put("error", ex.getMessage());
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.BAD_REQUEST.value());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
Map<String, Object> error = new HashMap<>();
error.put("error", "Internal server error");
error.put("details", ex.getMessage());
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
πΉ Connection Timeouts
Connection timeouts are critical in Spring WebClient to prevent your application from waiting indefinitely when calling slow or unresponsive services. Configuring timeouts ensures microservices remain responsive, resilient, and production-ready.
Types of Timeouts in WebClient
With WebClient (backed by Reactor Netty), there are three main timeout types:
Connection Timeout: Maximum time allowed to establish a TCP connection.
Response Timeout: Maximum time to wait for the first byte of the response.
Read/Write Timeout: Maximum time allowed for reading or writing data once connected.
Example: Configure Timeouts in WebClient
Configure timeouts when building a WebClient using Reactor Netty:
@Configuration
public class WebClientConfig {
@Bean
public WebClient.Builder webClientBuilder() {
HttpClient httpClient = HttpClient.create()
// 1. Connection Timeout
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
// 2. Response Timeout
.responseTimeout(Duration.ofSeconds(5))
// 3. Read & Write Timeout
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS))
.addHandlerLast(new WriteTimeoutHandler(5, TimeUnit.SECONDS))
);
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient));
}
}
Using Request-Level Timeout
Add a per-request timeout as an extra safety net:
HotelResponse hotel = webClient
.get()
.uri("hotel/{id}", booking.getHotelId())
.retrieve()
.bodyToMono(HotelResponse.class)
.timeout(Duration.ofSeconds(3))
.block();
πΉConclusion
In microservices, WebClient gives developers a modern and reactive way to handle HTTP communication. Even when used synchronously with .block(), it provides better flexibility and cleaner APIs compared to the legacy RestTemplate. As Spring continues to evolve, WebClient stands out as the go-to option for building robust service-to-service communication.
Top comments (0)