For nearly two decades, Java developers relied on HttpURLConnection for network requests. While functional, it was designed in an era before HTTP/2, WebSockets, and non-blocking I/O, leading to clunky and hard-to-maintain code. Java 11 changed everything by introducing a modern HTTP Client API.
What is the Java 11 HTTP Client?
The new HttpClient (JEP 321) is a modern replacement for legacy networking APIs. It resides in the java.net.http module and provides a fluent, builder-based API for creating requests and handling responses either synchronously or asynchronously.
Key pillars of this API:
- HttpClient: The engine that sends requests.
- HttpRequest: The builder for your request (URL, headers, method).
- HttpResponse: The object holding the results (status code, body).
Sending a Simple Synchronous Request
If you just need to fetch data and wait for it, a synchronous request is the easiest way to start.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class SyncRequestExample {
public static void main(String[] args) throws Exception {
// 1. Create a client
HttpClient client = HttpClient.newHttpClient();
// 2. Build the request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
.GET()
.build();
// 3. Send and get response
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 4. Print results
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
}
}
Going Asynchronous (Non-Blocking)
One of the biggest advantages of the new API is its native support for asynchronous requests. Instead of blocking your main thread, you can use CompletableFuture to handle the response whenever it arrives.
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 AsyncRequestExample {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/2"))
.build();
// Send asynchronously
CompletableFuture<HttpResponse<String>> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
// Attach callbacks
future.thenApply(HttpResponse::body)
.thenAccept(body -> System.out.println("Received: " + body))
.join(); // Only for demo purposes to prevent program exit
}
}
Support for HTTP/2
The new client supports both HTTP/1.1 and HTTP/2. By default, it prefers HTTP/2 if the server supports it, automatically falling back to HTTP/1.1 if necessary.
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
Real-World Example: Fetching Weather Data
Let’s imagine you are building a simple weather app. You need to send a custom header and handle potential errors.
public void fetchWeather(String city) {
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.weather.com?city=" + city))
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(10))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
if (response.statusCode() == 200) {
return response.body();
} else {
return "Error: " + response.statusCode();
}
})
.thenAccept(System.out::println)
.exceptionally(ex -> {
System.err.println("API Call Failed: " + ex.getMessage());
return null;
});
}
Conclusion
The Java 11 HTTP Client API is a breath of fresh air for Java developers. It is powerful, readable, and perfectly suited for modern microservices and reactive applications. By supporting HTTP/2 and providing a clean asynchronous model, it makes HttpURLConnection and third-party libraries less of a necessity for standard tasks.