Issue
I have created a reusable method that returns the headers, status, and body of an HTTP call using the Spring webclient's exchange
method. I want to capture the 4xx and 5xx errors in the method itself and log the response body. However, i am not able to return/throw custom exception when 4xx or 5xx error occurs.
Here is the reusable method -
private Mono<ResponseEntity<Mono<String>>> exchangeGet(AutomationHttpRequest request) throws AutomationException {
oLogger.debug("Invoking Exchange request for - {}", request.getName());
return webClient
.get()
.uri(request.getUri())
.headers(request.getHeaderList())
.exchangeToMono(Mono::just)
.map( response -> {
// Approach 1
// if(response.statusCode().is4xxClientError()){
// oLogger.error("Client side error for request {}, status {}", request.getName(), response.statusCode());
// oLogger.error("response body - ");
// response.toEntity(String.class).subscribe(error -> oLogger.error(String.valueOf(error.getBody())));
// throw new AutomationException("");
// }
//
// Approach 2
// if(response.statusCode().is4xxClientError()){
// oLogger.error("Client side error for request {}, status {}", request.getName(), response.statusCode());
// oLogger.error("response body - ");
// response.toEntity(String.class).subscribe(error -> oLogger.error(String.valueOf(error.getBody())));
// return Mono.error(new AutomationException(""));
// }
HttpHeaders headers = response.headers().asHttpHeaders().entrySet().stream()
.filter(entry -> !entry.getKey().equalsIgnoreCase("Transfer-Encoding"))
.collect(HttpHeaders::new,
(httpHeaders, entry) -> httpHeaders.addAll(entry.getKey(), entry.getValue()), HttpHeaders::putAll);
return ResponseEntity.status(response.statusCode())
.headers(headers).body(response.bodyToMono(String.class));
});
}
Note - AutomationException
is a custom exception class.
If you observe the commented out code, you will notice that in Approach 1, i tried throwing an exception, but it complains that the error is not handled, which means the function (map) is expected to handle it and not throw it from the reusable exchangeGet
method. Approach 2, does not work as the return parameterized Mono object's type does not match the method return type. I cannot use generics as there would be issues with Sonar lint.
Basically, the effect that I am trying to obtain is as follows -
private Mono<String> get(AutomationHttpRequest request) throws AutomationException {
oLogger.debug("Invoking GET request for - {}", request.getName());
return webClient
.get()
.uri(request.getUri())
.headers(request.getHeaderList())
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
oLogger.error(LOG_CLIENT_ERROR, request.getName(), response.statusCode());
response.toEntity(String.class).subscribe(error -> oLogger.error(String.valueOf(error.getBody())));
return Mono.error(new AutomationException(CLIENT_EXCEPTION));
}).onStatus(HttpStatus::is5xxServerError, response -> {
oLogger.error(LOG_SERVER_ERROR, request.getName(), response.statusCode());
response.toEntity(String.class).subscribe(error -> oLogger.error(String.valueOf(error.getBody())));
return Mono.error(new AutomationException(SERVER_EXCEPTION));
})
.bodyToMono(String.class);
}
In the above, retrieve
is used instead of exchange
and therefore I can handle the 4xx and 5xx errors individually. I am also able to log the status, and body in case of error sceanrios.
How do I achieve the same with exchange
method. I do not want to log the exception on the consumer class. Besides logging, i want to standardize the exception thrown in error scenarios such as the retrive
method.
Note - I need to use exchange
in order to access the response headers and body. If there is a better way to achieve the same, kindly advice.
Regards.
Solution
your problem is .exchangeToMono(Mono::just)
why you decided to just ignore this operator and then do all your logic in map
i just strange.
Map
is used to map one type to a another synchronously. Which means that you map it from type T
to type U
.
Why it doesn't work is because you are trying to map it from type T
to type U
and Mono<V>
in the same function. So what does this mean?
in your map function you are returning the type ResponseEntity
. But you also want to return a Mono.error
a couple of rows before. These are two different types and you can't return 2 different types from a function. Thats not how the type system works.
What you need to do is to return Mono
.
So remove the map
function and move your logic to the exchangeToMono
its litteraly the name of the function, here you turn your exchange into a Mono or a Mono.error.
Also use ServerResponse#build
instead of ResponseEntity
And all of this is documented in the docs for WebClient so please read the docs https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client-exchange
Answered By - Toerktumlare
Answer Checked By - Candace Johnson (JavaFixing Volunteer)