Home > Net >  With Spring WebClient, how do I deserialize different types for successful and failed responses?
With Spring WebClient, how do I deserialize different types for successful and failed responses?

Time:04-08

tbjorch's answer led me down the right path, but one thing I discovered is I should construct the HTTP exceptions using the static .create methods for both HttpClientErrorException and HttpServerErrorException. If I didn't do this, all my exceptions would get thrown as just the Exception superclass, which was undesirable since it lost the context of the original Http status code.

I am new to using Spring WebClient. The API I'm implementing responds with different JSON structures based on success or failure. In the case of a failure, I want to be able to deserialize the response body and return the error message. Based on the code below, I get an error message stating:

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83)

I agree, it doesn't make sense to block in 2 places. But I'm not understanding how I can deserialize the error response to grab the error message. Everything I read on the web and StackOverflow indicates people just return Mono.error or throw exceptions, but they don't seem to be deserializing the response body in those scenarios. Here's my code:

  public boolean updatePassword(String id, String data) {
    final var responseSpec =
        client
            .post()
            .uri(builder -> builder.path("/{id}/change_password").build(id))
            .header(HttpHeaders.AUTHORIZATION, "Bearer "   staticData.getAPIToken())
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromValue(data))
            .retrieve()
            .onStatus(
                HttpStatus::isError,
                error -> {
                  final var errorResponse = error.bodyToMono(ErrorResponse.class).block();
                  final var errorMsg = errorResponse.getCause().getSummary();

                  if (error.statusCode().is4xxClientError()) {
                    throw new HttpClientErrorException(error.statusCode(), errorMsg);
                  }
                  if (error.statusCode().is5xxServerError()) {
                    throw new HttpServerErrorException(error.statusCode(), errorMsg);
                  }
                  return Mono.error(
                      new HttpServerErrorException(
                          HttpStatus.INTERNAL_SERVER_ERROR, "Something else."));
                })
            .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {});

    final var response = responseSpec.block();

    return response.containsKey("password") && response.containsKey("provider");
  }

PS. I've also tried using .exchangeToMono instead of .retrieve, so that I can inspect the response status code, and then use different types for the .bodyToMono function, but as soon as I write two different types between a success and failure, I get an error stating that the type argument can't be inferred.

CodePudding user response:

Not able to try your code exactly, but try something like this:

public boolean updatePassword(String id, String data) {
    final var responseSpec =
        client
            .post()
            .uri(builder -> builder.path("/{id}/change_password").build(id))
            .header(HttpHeaders.AUTHORIZATION, "Bearer " /*  staticData.getAPIToken()*/)
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromValue(data))
            .retrieve()
            .onStatus(
                HttpStatus::isError,
                error -> error.bodyToMono(ErrorResponse.class)
                    .map(errorResponse -> {
                    final var errorMsg = errorResponse.getCause().getSummary();
                    if (error.statusCode().is4xxClientError()) {
                      throw new HttpClientErrorException(error.statusCode(), errorMsg);
                    }
                    if (error.statusCode().is5xxServerError()) {
                      throw new HttpServerErrorException(error.statusCode(), errorMsg);
                    }
                    return new HttpServerErrorException(
                            HttpStatus.INTERNAL_SERVER_ERROR, "Something else.");
                  }))
            .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {});

    final var response = responseSpec.block();

    return response.containsKey("password") && response.containsKey("provider");
  }
  • Related