Chaining asynchronous calls using Java 11’s HttpClient and CompletableFuture can be achieved by leveraging the reactive capabilities of CompletableFuture. The sendAsync method of HttpClient supports asynchronous processing, and you can chain multiple calls together using methods like thenApply, thenCompose, or thenAccept. Here’s a step-by-step example:
Key Concepts Used:
CompletableFuture:- Allows for async processing and chaining of dependent tasks.
HttpClientandHttpRequest:- The async calls are made using the
HttpClient.sendAsyncmethod.
- The async calls are made using the
- Chaining methods:
- Use
thenApplyto transform the response orthenComposeto chain dependent async calls.
- Use
Example: Chaining Multiple HTTP Requests
Say we need to:
- Fetch data using one API.
- Use the response data to make another API call.
- Process the final response.
Here’s how you can do that:
package org.kodejava.net.http;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
public class AsyncChainingExample {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
// First API Request
HttpRequest firstRequest = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
.GET()
.build();
// First Async Call
CompletableFuture<Void> future = client.sendAsync(firstRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body) // Extract body from response
.thenCompose(body -> {
System.out.println("First API Response: " + body);
// Use data from the first response to make the second API request
String secondApiUri = "https://jsonplaceholder.typicode.com/comments?postId=1";
HttpRequest secondRequest = HttpRequest.newBuilder()
.uri(URI.create(secondApiUri))
.GET()
.build();
return client.sendAsync(secondRequest, HttpResponse.BodyHandlers.ofString());
})
.thenApply(HttpResponse::body) // Extract body from second response
.thenAccept(secondResponse -> {
// Final result processing
System.out.println("Second API Response: " + secondResponse);
});
// Wait for all the tasks to complete
future.join();
}
}
Explanation of the Code:
- Create the HttpClient:
HttpClient.newHttpClient()initializes the HTTP client that will send requests asynchronously.
- First API Call:
- The first API request (
firstRequest) is created usingHttpRequest.newBuilder. - Send the request asynchronously with:
client.sendAsync(firstRequest, HttpResponse.BodyHandlers.ofString());thenApplyis used to extract thebodyof the response.
- The first API request (
- Second API Call (Chained):
- In
thenCompose, the code prepares and sends the second API request. This ensures that the second API call happens only after the first call completes. - The response of this call is again processed by extracting the body.
- In
- Response Processing:
thenAcceptis used at the end of the chain to process the final response.
- Waiting for Completion:
- Since the operations are asynchronous,
future.join()blocks the main thread until all the chained calls complete.
- Since the operations are asynchronous,
Output:
Sample output from the above example (when run):
First API Response: {
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati",
"body": "quia et suscipit..."
}
Second API Response: [
{
"postId": 1,
"id": 1,
"name": "id labore ex et quam laborum",
...
},
...
]
Key Functions Used in the Chain:
thenApply(Function)- Transforms the result of the previous step (e.g., extract the body).
thenCompose(Function)- Used for dependent async calls. Ensures one
CompletableFuturewaits for another.
- Used for dependent async calls. Ensures one
thenAccept(Consumer)- Consumes the result without returning anything.
Advantages of this Approach:
- No need for manual thread management.
- Non-blocking I/O.
- Easily scalable chaining of async calls.
This is a modern, clean solution for handling asynchronous HTTP requests in Java using HttpClient and CompletableFuture.
