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.
HttpClient
andHttpRequest
:- The async calls are made using the
HttpClient.sendAsync
method.
- The async calls are made using the
- Chaining methods:
- Use
thenApply
to transform the response orthenCompose
to 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());
thenApply
is used to extract thebody
of 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:
thenAccept
is 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
CompletableFuture
waits 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
.