When to Use WebClient
When building reactive applications (e.g., using Spring WebFlux).
When you need non-blocking I/O for better scalability and performance.
When working with streaming APIs or handling large datasets.
When you want to use functional programming for HTTP requests.
Dependency
Copy < dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-webflux</ artifactId >
</ dependency >
Sample Usages
Copy // get example
public < T > Mono< T > get( String url , Class< T > responseType , String token) {
return webClient . get ()
. uri ( getUriBuilderURIFunction(url) )
. header ( MessageConst . AUTHORIZATION , MessageConst . BEARER + token)
. retrieve ()
. bodyToMono (responseType)
. timeout (timeout)
. doOnSuccess (response -> log . info ( "Received successful response from GET {}" , url))
. doOnError (error -> log . error ( "Error in GET request to {}: {}" , url , error . getMessage ()))
. retryWhen (
Retry . backoff (retry , Duration . ofSeconds ( 1 )) . filter (e ->! e . getMessage () . equals ( "401" ))
. doBeforeRetry (retrySignal -> log . warn ( "Retrying GET request to {}" , url))
)
. onErrorResume ( this :: handleError);
}
// post example
public < T > Mono< T > post( String url , Object body , Class< T > responseType , String token) {
return webClient . post () . uri ( getUriBuilderURIFunction(url) )
. header ( MessageConst . AUTHORIZATION , MessageConst . BEARER + token)
. bodyValue (body)
. retrieve ()
. bodyToMono (responseType)
. timeout (timeout)
. doOnSuccess (response -> log . info ( "Received successful response from POST {}" , url))
. doOnError (error -> log . error ( "Error in POST request to {}: {}" , url , error . getMessage ()))
. onErrorResume ( this :: handleError);
}
Exception handling
We can use reactive extreame to do the handling . We have certain methods like doOnError , doOnStatus , onErrorResume etc, using them we can define how we want to handle it.
Copy // in web client
. onStatus (
HttpStatus :: is4xxClientError ,
clientResponse -> Mono . error ( new RuntimeException( "Client error occurred" ) )
)
. onStatus (
HttpStatus :: is5xxServerError ,
clientResponse -> Mono . error ( new RuntimeException( "Server error occurred" ) )
)
// or
. onErrorResume ( this :: handleError)
// custom handler method
private < T > Mono< T > handleError( Throwable error) {
if (error instanceof WebClientResponseException wcre) {
HttpStatusCode status = wcre . getStatusCode ();
String body = wcre . getResponseBodyAsString ();
if ( status . is4xxClientError ()) {
log . error ( "Client error: {} {}" , status , body);
return Mono . error ( new ClientException( "Client error: " + status + ", body: " + body) );
} else if ( status . is5xxServerError ()) {
log . error ( "Server error: {} {}" , status , body);
return Mono . error ( new ServerException( "Server error: " + status + ", body: " + body) );
}
} else if (error instanceof java . util . concurrent . TimeoutException ) {
log . error ( "Request timed out" );
return Mono . error ( new TimeoutException( "Request timed out after " + timeout . getSeconds() + " seconds" ) );
} else if ( Exceptions . isRetryExhausted (error)){
log . error ( "Retry exhausted " );
return Mono . error ( new TimeoutException( "Retry exhausted after " + retry + " attempts" ) );
}
log . error ( "Unexpected error" , error);
return Mono . error ( new GenericApiException( "An unexpected error occurred" , error) );
}
Log request and response of requests
To log request and reponse in webclient we can utilize the filters.
We can use ExchangeFilterFunction that have methods like ofRequestProcessor and ofResponseProcessor .
Create log filters for request and response and attach in configuration.
Copy private WebClient getBuilder( String applicationProperties) {
return WebClient . builder ()
. baseUrl (applicationProperties)
. defaultHeader ( HttpHeaders . CONTENT_TYPE , MediaType . APPLICATION_JSON_VALUE )
. filter ( logRequest() )
. filter ( logResponse() )
. build ();
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction . ofRequestProcessor (request -> {
log . info ( "Request: {} {}" , request . method () , request . url ());
return Mono . just (request);
});
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction . ofResponseProcessor (response -> {
log . info ( "Response: {}" , response . statusCode ());
return Mono . just (response);
});
}
Multiple web clients bean
Yes we can have multiple webclients bean present in the system. We need to create multiple beans with different bean names , then whenever required can use @Qualifier to inject the bean required.
Copy // in configuratrion
@ Bean
public WebClient userWebClient() {
return getBuilder( applicationProperties . getUserServiceUrl()) ;
}
@ Bean
public WebClient authWebClient() {
return getBuilder( applicationProperties . getAuthServiceUrl()) ;
}
@ Bean
public WebClient vendorWebClient() {
return getBuilder( applicationProperties . getVendorServiceUrl()) ;
}
// usages
@ Qualifier ( "vendorWebClient" ) WebClient webClient
Custom Headers and Query Parameters
Add custom headers and query parameters dynamically.
Copy public Mono< String > fetchDataWithCustomHeadersAndParams( String paramValue) {
return webClient . get ()
. uri (uriBuilder -> uriBuilder
. path ( "/data" )
. queryParam ( "param" , paramValue)
. build ())
. header ( "Custom-Header" , "value" )
. retrieve ()
. bodyToMono ( String . class );
}
Parallel requests
We can utilize zip method from reactive streams for this .
Copy public Mono< String > fetchParallelData() {
Mono < String > data1 = webClient . get ()
. uri ( "/data1" )
. retrieve ()
. bodyToMono ( String . class );
Mono < String > data2 = webClient . get ()
. uri ( "/data2" )
. retrieve ()
. bodyToMono ( String . class );
return Mono . zip (data1 , data2 , (d1 , d2) -> d1 + " " + d2);
}
Streaming Data
Can be used to stream data from an API (e.g., Server-Sent Events or large JSON arrays).
Copy public Flux< String > streamData() {
return webClient . get ()
. uri ( "/stream" )
. retrieve ()
. bodyToFlux ( String . class ); // Stream data as Flux
}
Caching
Implement caching for responses.
Copy public Mono< String > fetchDataWithCache() {
return webClient . get ()
. uri ( "/data" )
. retrieve ()
. bodyToMono ( String . class )
. cache ( Duration . ofMinutes ( 10 )); // Cache the response for 10 minutes
}
Reactive Caching with Redis
Use Redis for reactive caching of responses.
Copy public Mono< String > fetchDataWithRedisCache() {
return webClient . get ()
. uri ( "/data" )
. retrieve ()
. bodyToMono ( String . class )
. cache ( Duration . ofMinutes ( 10 ))
. flatMap (data -> redisTemplate . opsForValue () . set ( "cacheKey" , data) . thenReturn (data));
}
File Upload
Upload a file using WebClient
with multipart/form-data
.
Copy public Mono< String > uploadFile( File file) {
MultipartBodyBuilder builder = new MultipartBodyBuilder() ;
builder . part ( "file" , new FileSystemResource(file) );
return webClient . post ()
. uri ( "/upload" )
. body ( BodyInserters . fromMultipartData ( builder . build ()))
. retrieve ()
. bodyToMono ( String . class );
}
File Download
Download a file and save it to the local filesystem.
Copy public Mono< Void > downloadFile( String url , Path savePath) {
return webClient . get ()
. uri (url)
. retrieve ()
. bodyToMono ( Resource . class )
. flatMap (resource -> {
try {
Files . copy ( resource . getInputStream () , savePath , StandardCopyOption . REPLACE_EXISTING );
return Mono . empty ();
} catch ( IOException e) {
return Mono . error (e);
}
});
}
Custom Serialization/Deserialisation
Use custom serialization/deserialisation for requests and responses.
Copy public Mono< MyData > fetchDataWithCustomDeserialization() {
return webClient . get ()
. uri ( "/data" )
. retrieve ()
. bodyToMono ( new ParameterizedTypeReference < MyData >() {});
}
Custom SSL Configuration
Configure custom SSL settings .
Copy public WebClient createWebClientWithCustomSSL() {
SslContext sslContext = SslContextBuilder . forClient () . build ();
HttpClient httpClient = HttpClient . create () . secure (sslContextSpec -> sslContextSpec . sslContext (sslContext));
return WebClient . builder ()
. clientConnector ( new ReactorClientHttpConnector(httpClient) )
. build ();
}
Custom Metrics
Add custom metrics for monitoring WebClient
requests.
Copy public Mono< String > fetchDataWithMetrics() {
return webClient . get ()
. uri ( "/data" )
. retrieve ()
. bodyToMono ( String . class )
. metrics () // Add metrics
. name ( "webclient.requests" )
. tag ( "uri" , "/data" )
. register (registry); // attach to registery
}