I am attempting to implement a generic REST client as below. I have my own model to represent the HTTP response as Response T . Here T is the return type from the service call and it could be just T or List of Ts. Below code does not compile and that is where I need help. You can suggest if you know any better ways to accomplish the same task.
@Override
public <T> CompletableFuture<Response<T>> getAsync(UriOptions endpoint, Headers headers) {
var builder = HttpRequest.newBuilder()
.uri(endpoint.getAbsoluteUri())
.GET();
for (var entry : headers.getMessageHeaders().entrySet()) {
builder.setHeader(entry.getKey(), entry.getValue());
}
var request = builder.build();
var response = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString());
ObjectMapper objectMapper = new ObjectMapper();
return response.thenComposeAsync(r ->
try {
new Response<T>(
r.statusCode(),
HttpUtils.headersToMap(r.headers().map()),
objectMapper.readValue(r.body(), new TypeReference<T>() {
}),
HttpUtils.isSuccess(r.statusCode()), r.uri()
);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
);
}
public class Response<T> {
private T content;
private int statusCode;
private boolean isSuccessful;
private String statusDescription;
private Exception exception;
private Map<String, String> headers;
private URI uri;
And eventually call the method as below,
@Test
void getAsync() {
// Arrange
var baseUrl = settings.get("api.baseUri");
var options = new UriOptions(baseUrl, "students/6277aade6f047803e8ae4bcf");
var headers = new Headers(new HashMap<String, String>(), new HashMap<String, String>());
// Act
var response = restApiClient.<Student>getAsync(options, headers);
// Assert
response.thenAccept((r) ->
{
Assertions.assertEquals(200, r.getStatusCode());
});
}
To answer the question "How to return a CompletableFuture<MyType> from a CompletableFuture<AnotherType>" from the title: you'd generally use
thenApply
and friends:Looking at your specific code, there seems to be a syntax error in the lambda of
thenComposeAsync
(which composes two CompletableFuture instances, so its lambda needs to return another CompletableFuture instance, not the value itself).And since it is not doing any expensive work, transforming the value asynchronically is overkill. A simple
thenApply
will do the job just fine:If you insist on using
thenCompose
, then you need to wrap your result in another CompletableFuture: