Wisdom
  • Welcome
  • core
    • Flyway
    • Bean Validation
    • Lombok
    • Webclient
      • Generic Webclient Api
      • SSL Certificate
    • Application Event Publisher
    • REST API's Design
      • Http Methods and Status Codes
      • Resource Naming and URL Structure
      • Request / Response Design
      • Versioning Strategies
      • Filtering and Searching In API
    • Spring Boot Mail Integration
      • Sendgrid
    • Template Engines
      • Java Template Engine [JTE]
  • security
    • Complete Guide to URL Matchers in Spring Security: Types, Examples, Pros, Cons, and Best Use Cases
    • Passwordless Login With Spring Security 6
      • Spring Security OneTimeToken
        • One Time Token with default configuration
        • One Time Token with custom configuration
        • One Time Token With Jwt
  • others
    • How to Integrate WhatsApp for Sending Messages in Your Application
  • java
    • Interview Questions
      • Constructor
      • Serialization
      • Abstract Class
    • GSON
      • Type Token
      • Joda Datetime Custom Serializer and Deserializer
  • Nginx
    • Core Concepts and Basics
    • Deep Dive on NGINX Configuration Blocks
    • Deep Dive on NGINX Directives
    • Deep Dive into Nginx Variables
    • Nginx as a Reverse Proxy and Load Balancer
    • Security Hardening in NGINX
    • Performance Optimization & Tuning in NGINX
    • Dynamic DNS Resolution in Nginx
    • Advanced Configuration & Use Cases in NGINX
    • Streaming & Media Delivery in NGINX
    • Final Configuration
  • Angular
    • How to Open a PDF or an Image in Angular Without Dependencies
    • Displaying Colored Logs with Search Highlighting in Angular 6
    • Implementing Auto-Suggestion in Input Field in Angular Template-Driven Forms
    • Creating an Angular Project Using npx Without Installing It Globally
    • Skip SCSS and Test Files in Angular with ng generate
  • Javascript
    • When JavaScript's Set Falls Short for Ensuring Uniqueness in Arrays of Objects
    • Demonstrating a Function to Get the Last N Months in JavaScript
    • How to Convert Numbers to Words in the Indian Numbering System Using JavaScript
    • Sorting Based on Multiple Criteria
  • TYPESCRIPT
    • Using Omit in TypeScript
Powered by GitBook
On this page
  • When to Use WebClient
  • Dependency
  • Sample Usages
  • Exception handling
  • Log request and response of requests
  • Multiple web clients bean
  • Custom Headers and Query Parameters
  • Parallel requests
  • Streaming Data
  • Caching
  • Reactive Caching with Redis
  • File Upload
  • File Download
  • Custom Serialization/Deserialisation
  • Custom SSL Configuration
  • Custom Metrics
  1. core

Webclient

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Sample Usages

// 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.

// 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.

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.

// 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.

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 .

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).

public Flux<String> streamData() {
    return webClient.get()
        .uri("/stream")
        .retrieve()
        .bodyToFlux(String.class); // Stream data as Flux
}

Caching

Implement caching for responses.

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.

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.

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.

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.

public Mono<MyData> fetchDataWithCustomDeserialization() {
    return webClient.get()
        .uri("/data")
        .retrieve()
        .bodyToMono(new ParameterizedTypeReference<MyData>() {});
}

Custom SSL Configuration

Configure custom SSL settings .

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.

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
}

PreviousLombokNextGeneric Webclient Api

Last updated 5 months ago