I'm making a post with a WebClient and I want to throw various errors based off of the status code and error message that we get back. To do that, I have an awaitExchange function that looks like this:

webClient.post()
       ...
       .awaitExchange { clientResponse ->
                if (clientResponse.statusCode().is4xxClientError) {
                    val errorString = clientResponse.awaitBody<String>()
                    throw Custom4xxException(
                        errorString,
                        clientResponse.statusCode().value(),
                    )
                } else if (clientResponse.statusCode().is5xxServerError) {
                    val errorString = clientResponse.awaitBody<String>()
                    throw Custom5xxException(
                        errorString,
                        clientResponse.statusCode().value(),
                    )
                }
                return@awaitExchange clientResponse.awaitBody<TokenServerResponse>()
            }

However, this thrown error never actually shows up in the logs as any kind of stacktrace. It seems to just be getting completely swallowed. Whereas, if I use the built-in onStatus() function like this

webClient.post()
                 ...
                 .retrieve()
                 .onStatus(HttpStatus.Series.CLIENT_ERROR::equals) {
                      throw FanIdClientException("SUPER CRITICAL ERROR", it.statusCode().value())
                  }

the error does get successfully thrown in the logs, but I have no way of getting the returned error message from the response body into the thrown exception.

The ideal solution would be that the thrown exception from my awaitExchange() function call would show up as a proper exception within the logs, instead of just not showing up at all. Any insight on this would be appreciated.

Comment From: sdeleuze

I tried to reproduce but was not able since I see the uncatched error in the log, please provide a reproducer.

Comment From: d-wire

@sdeleuze I'm able to reproduce it using the above awaitExchange() function, as well as the custom exceptions set up like this:

class Custom4xxException(message: String, val statusCode: Int) : RuntimeException(message)

and a global controller error handler like this

@ControllerAdvice
class GlobalExceptionHandler {
    @ExceptionHandler(Custom4xxException::class)
    fun handleClientException(e: Custom4xxException): ResponseEntity<ErrorMessageModel> {
        val errorMessage = ErrorMessageModel(
            e.statusCode,
            e.message,
        )
        return ResponseEntity(errorMessage, HttpStatusCode.valueOf(e.statusCode))
    }}

Comment From: sdeleuze

By reproducer, I mean please share a repository or attach an archive with the project that allows to reproduce, and I will have a deeper look.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: d-wire

We actually just found out that we had a mechanism in place that was swallowing all of our errors. Feel free to close!

Comment From: sbrannen

Thanks for the feedback, @d-wire.

Closing as a result.