2

Currently, I have this JsonBodyHandler used to parse the response body if the response is successful. I also have a model defined if the request is not successful. So based on the status code, I need to map the response to either the expected or exception model classes.

  private static class JsonBodyHandler<R> implements HttpResponse.BodyHandler<Supplier<R>> {

    private final Class<R> returnClz;

    public JsonBodyHandler(Class<R> returnClz) {
      this.returnClz = returnClz;
    }

    private static <R> HttpResponse.BodySubscriber<Supplier<R>> asJson(Class<R> returnClz) {
      HttpResponse.BodySubscriber<InputStream> upstream =
          HttpResponse.BodySubscribers.ofInputStream();

      return HttpResponse.BodySubscribers.mapping(
          upstream, inputStream -> toSupplierOfType(inputStream, returnClz));
    }

    private static <R> Supplier<R> toSupplierOfType(InputStream inputStream, Class<R> returnClz) {
      return () -> {
        try (InputStream stream = inputStream) {
          ObjectMapper objectMapper = new ObjectMapper();
          return objectMapper.readValue(stream, returnClz);
        } catch (IOException e) {
          throw new UncheckedIOException(e);
        }
      };
    }

    @Override
    public HttpResponse.BodySubscriber<Supplier<R>> apply(HttpResponse.ResponseInfo responseInfo) {
      return asJson(returnClz);
    }
  }

I'm sending requests as follows:

  <B, R> R exchange(URI uri, String method, B body, Class<R> returnClz) {
    Builder httpRequestBuilder = HttpRequest.newBuilder().uri(uri);
    addHeaders(httpRequestBuilder);

    var httpRequest =
        "GET".equals(method)
            ? httpRequestBuilder.GET().build()
            : httpRequestBuilder.method(method, getBodyPublisher(body)).build();

    var httpClient = HttpClient.newHttpClient();
    JsonBodyHandler<R> bodyHandler = new JsonBodyHandler<>(returnClz);
    Supplier<R> responseSupplier = httpClient.send(httpRequest, bodyHandler).body();
    return responseSupplier.get();
  }

What I want to do is something as follows:

    BodySubscriber<Supplier<R>> successBodySubscriber = JsonBodyHandler.asJson(returnClz);
    BodySubscriber<Supplier<ExceptionModel>> failureBodySubscriber =
        JsonBodyHandler.asJson(ExceptionModel.class);
    BodyHandler<Supplier> jsonBodyHandler =
        (rspInfo) -> rspInfo.statusCode() == 200 ? successBodySubscriber : failureBodySubscriber;

    HttpResponse<Supplier> httpResponse = httpClient.send(httpRequest, jsonBodyHandler);
    if (httpResponse.statusCode() != 200) {
      Supplier<ExceptionModel> responseSupplier = httpResponse.body();
      throw ClientServiceError.invalidResponse(responseSupplier.get());
    }

    Supplier<R> responseSupplier = httpResponse.body();
    return responseSupplier.get();

This doesn't work, I get compile time error at line BodyHandler<Supplier> jsonBodyHandler = (rspInfo) -> rspInfo.statusCode() == 200 ? successBodySubscriber : failureBodySubscriber;:

Incompatible types. Found: 'java.net.http.HttpResponse.BodySubscriber<java.util.function.Supplier>', required: 'java.net.http.HttpResponse.BodySubscriber<java.util.function.Supplier>'

Incompatible types. Found: 'java.net.http.HttpResponse.BodySubscriber<java.util.function.Supplier<com.project.proxy.impl.ExceptionModel>>', required: 'java.net.http.HttpResponse.BodySubscriber<java.util.function.Supplier>'

I am using Java 11. Cannot upgrade the version. I believe there should be a better way to do this. Any suggestions will be appreciated.

11
  • Please elaborate on “doesn’t work”. Commented Apr 30, 2023 at 20:07
  • It doesn't compile. Also, this is using Raw Types. Commented Apr 30, 2023 at 20:15
  • At the very least, I believe it would be better to do the status code check inside the apply method of your JsonBodyHandler class. Commented Apr 30, 2023 at 20:56
  • 1
    Then I would try to unify the result type, maybe behind an interface. If using Java 17+ I might even try to implement this using a sealed interface, then have two records to describe the result as a success or a failure. The Success record could hold the real object (of type R in your example), and the Failure record could just carry an error message as a String. If allowed to use preview features, then perhaps pattern matching for switch (currently its 4th preview in Java 20) [cont.] Commented Apr 30, 2023 at 21:02
  • 1
    [cont.] could be used to process the result. Though a basic instanceof check could work instead (also see pattern matching for instanceof). Note I'm mostly just musing here. There are likely some details that would need to be worked out. But this is a pattern I've seen in Kotlin quite a few times (i.e., sealed class/interface, limited number of data class and/or object implementations, using a when expression to process the result). Commented Apr 30, 2023 at 21:05

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.