# 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

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

### Sample Usages

```java
// 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&#x20;

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.

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

```java
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.

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

```java
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&#x20;

We can utilize zip method from reactive streams for this .

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

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

### &#x20;**Caching**

Implement caching for responses.

```java
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.

```java
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`.

```java
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.

```java
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.

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

### **Custom SSL Configuration**

Configure custom SSL settings .

```java
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.

```java
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
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wisdom.gitbook.io/gyan/core/webclient.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
