Skip to content
Advertisement

How to throw custom exception when using Spring webclient exchange method?

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.

Advertisement

Answer

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

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement