What am I trying to do:
Replace an old RestTemplate
call with WebClient
synchronous call , as per Spring recommendation to avoid using RestTemplate and as part of SpringBoot upgrade.
What behavior I want to achieve:
What I coded:
onStatus
to translate 4xx to ApiInvokerClientErrorException
and to translate other non 2xx status codes to ApiInvokerErrorException
, while passing the status code and response body as parameters to those exceptions.filter
and ApiInvokerClientErrorException
class to avoid retry in case of 4xx erroronRetryExhaustedThrow
to extract status code and response body from ApiInvokerErrorException
/ApiInvokerClientErrorException
and build the required dedicated exception that is thrown.The code:
String responseJson = WebClient.create()
.method(httpMethod)
.uri(url, uriBuilder -> uriBuilder.queryParams(queryParams).build())
.bodyValue(body)
.retrieve()
.onStatus(status -> status.is4xxClientError(),
response -> Mono.error(new ApiInvokerClientErrorException(response.bodyToMono(String.class).block(Duration.ZERO), response.statusCode())))
.onStatus(status -> (!status.is2xxSuccessful() && !status.is4xxClientError()),
response -> Mono.error(new ApiInvokerException(response.bodyToMono(String.class).block(Duration.ZERO), response.statusCode())))
.bodyToMono(String.class)
.retryWhen(Retry.fixedDelay(3, Duration.ofMillis(delayBetweenRetriesInMillis))
.filter(throwable -> !(throwable instanceof ApiInvokerClientErrorException))
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
if (retrySignal.failure() instanceof ApiInvokerException e) {
throw new UniformExceptionThatNeedToBeThrownOnError(e.getResponseBody(), e.getHttpStatus());
}
else {
throw new RuntimeException("The following call failed after 3 attempts: " + httpMethod.name() + url, retrySignal.failure());
}
}))
.block();
The exception classes are implemented as following:
public class ApiInvokerException extends Exception {
private String responseBody;
private HttpStatusCode httpStatusCode;
public ApiInvokerException(String responseBody, HttpStatusCode statusCode) {
super("Some message);
this.responseBody = responseBody;
this.httpStatusCode = statusCode.value();
}
}
public class ApiInvokerClientErrorException extends ApiInvokerException {
public ApiInvokerClientErrorException(String responseBody, HttpStatusCode statusCode) {
super("Some message", responseBody, statusCode);
}
}
Problem:
It is not a working solution since I can't block in onStatus
(getting java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-epoll-2
)
Question: How can I extract response body from error so I can add it to thrown exception when number of retries is exhausted (either by reaching 3 or by reaching not retrying due to 4xx code)
Consider using WebClientResponseException
, example code:
Mono<String> responseJson = WebClient.create()
.method(HttpMethod.GET)
.uri(u -> u.scheme("http").host("localhost").port(8080).path("test").build())
.retrieve()
.bodyToMono(String.class)
.retryWhen(
Retry.fixedDelay(3, Duration.ofMillis(1000))
.filter(throwable -> !(throwable instanceof WebClientResponseException ex
&& ex.getStatusCode().is4xxClientError())
)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> retrySignal.failure())
);
StepVerifier.create(responseJson)
.consumeErrorWith(ex -> {
WebClientResponseException webClientResponseException = (WebClientResponseException) ex;
System.out.println(ex);
System.out.println(webClientResponseException.getResponseBodyAsString());
})
.verify();