Beware of using spring WebClient(Episode4)

Change flatMap to map and onErrorResume to onErrorMap

AS-IS

Let's see the below code. I use clientResponse.createException().flatMap() and onErrorResume. However That is inefficient code. because flatMap unwrap and wrap to Mono.error again. onErrorResume is likewise.

.retrieve()
.onStatus(
  httpStatus -> httpStatus != HttpStatus.OK,
    clientResponse -> {
      return clientResponse.createException()
         .flatMap(it -> Mono.error(new RuntimeException("code : " + clientResponse.statusCode())));
      })
  .bodyToMono(String.class)
  .onErrorResume(throwable -> {
    return Mono.error(new RuntimeException(throwable));
});

TO-BE

So I can change flatMap to map and onErrorResume to onErrorMap. I remove inefficient code wrapping.

.retrieve()
.onStatus(
  httpStatus -> httpStatus != HttpStatus.OK,
    clientResponse -> {
      return clientResponse.createException()
         .map(it -> new RuntimeException("code : " + clientResponse.statusCode()));
      })
  .bodyToMono(String.class)
  .onErrorMap(throwable -> {
    return new RuntimeException(throwable);
});

※Beware

We have to use flatMap when we use exchangeToMono or exchangeToFlux. Let's see the below code. If I use map operator, It returns Mono<RuntimeException> type. However, clientResponse.bodyToMono(String.class) returns Mono<String> type so in that case, we have compile error.

.exchangeToMono(clientResponse -> {
  if (clientResponse.rawStatusCode() == HttpStatus.BAD_REQUEST.value()) {
    // It returns Mono<RuntimeException> type and makes compile error
    clientResponse
      .createException()
      .map(it -> new RuntimeException("400!!"));

    // Mono<String> type
    return clientResponse
      .createException()
      .flatMap(it -> Mono.error(new RuntimeException("400!!")));
  }

  if (clientResponse.rawStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
    return clientResponse
      .createException()
      .flatMap(it -> Mono.error(new RuntimeException("500!!")));
  }

  // Mono<String> type(success)
  return clientResponse.bodyToMono(String.class);
})

cf) Mono.error maintains the original T type so Mono<String> is return type.

public static <T> Mono<T> error(Throwable error) {
  return onAssembly(new MonoError(error));
}

We can use onErrorMap in other way. If the request fail because of timeout, WebClient retry 3 times. But If It failed 3 times all, RetryExhaustedException occurs. And in that case, We get origin error using Throwable::getCause.

.retryWhen(
    Retry
      .fixedDelay(3, Duration.ofMillis(500))
      .filter(YBSUtils::isTimeoutError)
)
.onErrorMap(
  Exceptions::isRetryExhausted,
  Throwable::getCause
)
...