5

When using the retrieve() method of Spring WebClient in conjunction with bodyToMono default error handling is applied (if the response has status code 4xx or 5xx, the Mono will contain a WebClientException). In error cases the resulting WebClientException is very helpful because it contains the request AND the response, which is very convenient e.g. for logging. Furthermore, I can react to errors very nicely in place.

/*
 * Easy to use retrieve() with default error handling:
 * By default, if the response has status code 4xx or 5xx, the Mono will contain a WebClientException - AWESOME!
 */
public Optional<MyType> getMyType() {
    try {
        MyType result = client
                .get()
                .uri("/myType")
                .retrieve().bodyToMono(MyType.class).block();

        return Optional.ofNullable(result);
    } catch (NotFound e) {
        return Optional.empty();
    }
}

However, sometimes I need to know the exact status code of the response to react to it when further processing this response. The only method to also get the status code is to call the exchange() Method instead of the retrieve() Method. Unfortunately, in that case the default error handling is not applied. The reason for that seems to be that calling bodyToMono() on ClientResponse has a different semantics than calling it on ResponseSpec.

I cannot call the already implemented Methods from Spring to "trigger" the error handling because all the nice methods are buried as private in the WebClient.

Even if I would try to "manually" create a WebClientResponseException I cannot access the request to feed to the exception.

public String updateMyType() {
    ClientResponse response = client
            .put()
            .uri("/myType")
            .header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
            .body(fromObject(new MyType()))
            .exchange()
            .block();

    if (response.statusCode().equals(HttpStatus.CREATED)) {
        return "OK";
    } else if (response.statusCode().equals(HttpStatus.NO_CONTENT)) {
        return "updated";
    } else {
        byte[] bodyBytes = null; // already implemented by Sping but not accessible
        Charset charset = null; // already implemented by Sping but not accessible
        HttpRequest request = null; // where should I get this from?
        throw WebClientResponseException.create(response.statusCode().value(),
                response.statusCode().getReasonPhrase(),
                response.headers().asHttpHeaders(),
                bodyBytes,
                charset,
                request);
    }
}

What is the best way to “mimic” the same behavior as for the retrieve() Method?

Fable
  • 371
  • 1
  • 4
  • 11

2 Answers2

4

This likely wasn't the case when the question was answered, but since Spring 5.2, ClientResponse has a createException() method that will build and return a Mono<WebClientResponseException>.

clientResponse.createException()
              .flatMap(e -> handleException(e))

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html#createException--

https://github.com/spring-projects/spring-framework/commit/b4207823afa0f48e06d6bbfe9ae8821e389e380f

olan
  • 3,568
  • 5
  • 24
  • 30
2

I faced a considerable struggle with this one as well.

My solution draws inspiration from the approach by @olan, but my aim was to employ identical error handling to that of the retrieve() method. Unfortunately, the guidance provided in the Spring Docs do not work. I had to resort to using flatmap(Mono::error) as elaborated here.

return webClient
  .get()
  .uri(
    // do stuff
  )
  .headers(
    // do stuff 
  )
  .exchangeToMono(
    response - > {
      if (response.statusCode().equals(HttpStatus.OK)) {
        return response.toEntity(JsonNode.class);
      } else {
        return response.createException().flatMap(Mono::error);
      }
    })
  .doOnNext(
    res - >
    // do stuff
    .block();
  }
catch (WebClientResponseException e) {
  throw new CustomError(e);
}

I'm utilizing Spring 5.3.3.

Arthur Met
  • 51
  • 4