V parseResponse(AggregatedHttpResponse response, BodyConverter<V> bodyConverter)
    throws IOException {
    // Handle the case where there is no content, as that means we have no resources to release.
    HttpStatus status = response.status();
    if (response.content().isEmpty()) {
      if (status.codeClass().equals(HttpStatusClass.SUCCESS)) {
        return null;
      } else if (status.code() == 404) {
        throw new FileNotFoundException(request.headers().path());
      } else {
        throw new RuntimeException(
          "response for " + request.headers().path() + " failed: " + response.status());
      }
    }

    // If this is a client or server error, we look for a json message.
    if ((status.codeClass().equals(HttpStatusClass.CLIENT_ERROR)
      || status.codeClass().equals(HttpStatusClass.SERVER_ERROR))) {
      bodyConverter = (parser, contentString) -> {
        String message = null;
        try {
          JsonNode root = OBJECT_MAPPER.readTree(parser);
          message = maybeRootCauseReason(root);
          if (message == null) message = root.at("/Message").textValue();
        } catch (RuntimeException | IOException possiblyParseException) {
          // EmptyCatch ignored
        }
        throw new RuntimeException(message != null ? message
          : "response for " + request.headers().path() + " failed: " + contentString.get());
      };
    }

    try (HttpData content = response.content();
         InputStream stream = content.toInputStream();
         JsonParser parser = JSON_FACTORY.createParser(stream)) {

      if (status.code() == 404) throw new FileNotFoundException(request.headers().path());

      return bodyConverter.convert(parser, content::toStringUtf8);
    }
  }
Returns the {@linkplain HttpHeaders headers} for this request.
zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/internal/client/HttpCall.java