Spring Boot: How to implement Generic WebClient for All MS

Satish Dixit
2 min readJun 26, 2023

--

In Spring, We have new Web Client API which replaces the existing RestTemplate API to perform both synchronous
and asynchronous call.

In Spring boot Microservices projects , we can use same webclient to call other microservices.
In order to call the microservice we need to configure the Webclient in Microservice using below:-

@Configuration
public class WebClientConfig implements WebFluxConfigurer {

@Bean public WebClient.Builder webClient() {
HttpClient httpClient = HttpClient.create(
ConnectionProvider.builder("test-webclient-connection").maxConnections(1000).build());

ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);

return WebClient.builder().clientConnector(connector)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
}

}

Same boilerplate code we need to configure in all microservices in order to call other microservice endpoint. Disadvantage with this approach is everywhere we need to handle the duplicate code ,retry logic and objects specific to rest endpoints.

Instead we can configure Generic Webclient which can accept all request and response and perform all the necessary action in separate jar and we can use the same jar in other microservices.

Sample code is mentioned below:-

private final WebClient.Builder webClientBuilder;


@Autowired
public GenericWebClient(WebClient.Builder webClientBuilder) {
this.webClientBuilder = webClientBuilder;
}

@Retryable(value = {HttpServerErrorException.class, WebClientRequestException.class, WebClientResponseException.class},
maxAttemptsExpression = "${generic.webclient.endpoint.maxAttempts:4}",
backoff = @Backoff(delayExpression = "${generic.webclient.endpoint.maxDelay:4000}"))
public <T> ResponseEntity<GenericResponse<T>> execute(GenericRequest genericRequest, Object requestEntity) throws HttpServerErrorException, WebClientRequestException, WebClientResponseException {
Mono<GenericResponse<T>> response = null;
AtomicInteger cnt = new AtomicInteger(1);

try {
response = this.webClientBuilder.build().method(genericRequest.getHttpMethod())
.uri(genericRequest.getServiceUrl())
.bodyValue(requestEntity)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<GenericResponse<T>>() {
})
.doOnNext(apiResponse -> logAPIResponse(genericRequest.getServiceUrl(), apiResponse));

} catch (HttpServerErrorException var6) {
this.handleHttpServerErrorException(var6);
} catch (WebClientRequestException var7) {
if (var7.getCause() instanceof ConnectException) {
log.error("Retry method called - {}", cnt.getAndIncrement());
log.error("WebClientRequestException Connection Timed Out Exception occurs in GenericWebClient ", var7);
throw var7;
}
} catch (WebClientResponseException var8) {
if (var8.getStatusCode().is5xxServerError()) {
log.error("WebClientResponseException Connection Timed Out Exception occurs in GenericWebClient ", var8);
throw var8;
}
}
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class GenericResponse<T> {
private T responseEntity;
private GenericError genericError;
}

As you can see above, Code performs retry as well and it accepts Java generics which makes it easier to maintain the code in single place.

Enjoy Learning

--

--

Satish Dixit

Experienced Software Engineering Lead | Delivering Excellence in Software Solutions | Architecting Scalable Solutions and Mentoring Technical Talent