title: In Spring WebClient used with block(), get body on error
body: <p>I am using WebClient from Spring WebFlux to communicate with a REST API backend from a Spring client.</p>
  <p>When this REST API backend throws an exception, it answers with a specific format (ErrorDTO) that I would like to collect from my client.</p>
  <p>What I have tried to do is to make my client throw a GestionUtilisateurErrorException(ErreurDTO) containing this body once the server answers with a 5xx HTTP status code.</p>
  <p>I have tried several options :</p>
  <p>I/ <strong>onStatus</strong></p>
  <pre><code>@Autowired
  WebClient gestionUtilisateursRestClient;
  
  gestionUtilisateursRestClient
      .post()
      .uri(profilUri)
      .body(Mono.just(utilisateur), UtilisateurDTO.class)
      .retrieve()
      .onStatus(HttpStatus::is5xxServerError,
          response -&gt; {
              ErreurDTO erreur = response.bodyToMono(ErreurDTO.class).block();
              
              return Mono.error(new GestionUtilisateursErrorException(erreur));
          }
      )   
      .bodyToMono(Void.class)
      .timeout(Duration.ofMillis(5000))         
      .block();
  </code></pre>
  <p>This method doesn't work because webclient doesn't allow me to call the <strong>block</strong> method in the <strong>onStatus</strong>. I am only able to get a Mono object and I can't go further from here.</p>
  <p>It seems like &quot;onStatus&quot; method can't be used in a WebClient blocking method, which means I can throw a custom Exception, but I can't populate it with the data from the response body.</p>
  <p>II/ <strong>ExchangeFilterFunction</strong></p>
  <pre><code>@Bean
  WebClient gestionUtilisateursRestClient() {
      return WebClient.builder()
        .baseUrl(gestionUtilisateursApiUrl)
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .filter(ExchangeFilterFunction.ofResponseProcessor(this::gestionUtilisateursExceptionFilter))
        .build();
  }   
  
  private Mono&lt;ClientResponse&gt; gestionUtilisateursExceptionFilter(ClientResponse clientResponse) {
      if(clientResponse.statusCode().isError()){
          return clientResponse.bodyToMono(ErreurDTO.class)
              .flatMap(erreurDto -&gt; Mono.error(new GestionUtilisateursErrorException(
                  erreurDto
              )));
      }
      
      return Mono.just(clientResponse);
  }
  </code></pre>
  <p>This method works but throw a reactor.core.Exceptions$ReactiveException that I am struggling to catch properly (reactor.core.Exceptions is not catchable, and ReactiveException is private).</p>
  <p>This Exception contains in its Cause the exception I need to catch (GestionUtilisateurErrorException) but I need a way to catch it properly.</p>
  <p>I also tried to use &quot;onErrorMap&quot; and &quot;onErrorResume&quot; methods but none of them worked the way I needed.</p>
  <p><strong>Edit 1 :</strong>
  I am now using the following workaround even if I feel it's a dirty way to do what I need :</p>
  <pre><code>gestionUtilisateursRestClient
              .post()
              .uri(profilUri)
              .body(Mono.just(utilisateur), UtilisateurDTO.class)
              .retrieve()
              .onStatus(h -&gt; h.is5xxServerError(),
                response -&gt; {
                      return response.bodyToMono(ErreurDTO.class).handle((erreur, handler) -&gt; {                         
                        LOGGER.error(erreur.getMessage());
                        handler.error(new GestionUtilisateursErrorException(erreur));
                      });
                    }
                  )
              .bodyToMono(String.class)
              .timeout(Duration.ofMillis(5000))
              
              .block();
      }
      catch(Exception e) {
          LOGGER.debug(&quot;Erreur lors de l'appel vers l'API GestionUtilisateur (...)&quot;);
          if(ExceptionUtils.getRootCause(e) instanceof GestionUtilisateursErrorException) {
              throw((GestionUtilisateursErrorException) e.getCause());
          }
          else {
              throw e;
          }
      }
  </code></pre>
  <p>Here, it throws the expected GestionUtilisateursErrorException that I can handle synchronously.</p>
  <p>I might implement this in a global handler to avoid writing this code around each call to my API.</p>
  <p>Thank you.
  Kevin</p>