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.
applymethod of yourJsonBodyHandlerclass.Successrecord could hold the real object (of typeRin your example), and theFailurerecord could just carry an error message as aString. If allowed to use preview features, then perhaps pattern matching for switch (currently its 4th preview in Java 20) [cont.]instanceofcheck 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 ofdata classand/orobjectimplementations, using awhenexpression to process the result).