1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: HTTP Client API (Java 11+) replaces legacy HttpURLConnection—modern, async-first, HTTP/2 support.
- Three key classes: HttpClient (reusable client), HttpRequest (request builder), HttpResponse (response).
- Two modes: Synchronous (send()) blocks, Asynchronous (sendAsync()) returns CompletableFuture.
- BodyHandlers: ofString(), ofFile(), ofInputStream().
- Benefits: Fluent builder API, async by default, HTTP/2 multiplexing, WebSocket support.
- Replace: Apache HttpClient, OkHttp (for simple use cases)!
Think of ordering food. Old HttpURLConnection = phone call (wait on hold, blocking). New HTTP Client = food app (order, do other things, notification when ready). Async-first design!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- Synchronous send(): Waiting in line (blocks until served)
- Asynchronous sendAsync(): Take number, sit down, called when ready
- HTTP/2: Multiple orders at once (multiplexing)
3. Technical Mastery (The "Deep Dive")
HTTP Client Components
| Component | Purpose |
|---|---|
| HttpClient | Reusable client (configure once, use many times) |
| HttpRequest | Request configuration (URL, headers, body) |
| HttpResponse | Response (status, headers, body) |
| BodyHandler | How to process response body |
| BodyPublisher | How to send request body |
4. Interactive & Applied Code
java
import java.net.*;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
import java.util.concurrent.*;
public class HTTPClientDemo {
public static void main(String[] args) throws Exception {
demonstrateGET();
demonstratePOST();
demonstrateAsync();
demonstrateAdvanced();
}
// Basic GET request (synchronous)
static void demonstrateGET() throws Exception {
System.out.println("=== GET REQUEST ===");
// Create client (reusable)
HttpClient client = HttpClient.newHttpClient();
// Build request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/get"))
.GET() // Default, can omit
.build();
// Send synchronously
HttpResponse<String> response = client.send(
request,
BodyHandlers.ofString()
);
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}
// POST request with JSON
static void demonstratePOST() throws Exception {
System.out.println("\n=== POST REQUEST ===");
HttpClient client = HttpClient.newHttpClient();
String jsonBody = """
{
"name": "Alice",
"age": 30
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = client.send(
request,
BodyHandlers.ofString()
);
System.out.println("Status: " + response.statusCode());
System.out.println("Response: " + response.body().substring(0, 100) + "...");
}
// Asynchronous request
static void demonstrateAsync() throws Exception {
System.out.println("\n=== ASYNC REQUEST ===");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/delay/2"))
.build();
// Send asynchronously (non-blocking!)
CompletableFuture<HttpResponse<String>> futureResponse =
client.sendAsync(request, BodyHandlers.ofString());
System.out.println("Request sent... doing other work...");
// Chain operations
futureResponse
.thenApply(HttpResponse::body)
.thenAccept(body -> System.out.println("Received: " + body.substring(0, 50)))
.join(); // Wait for completion
}
// Advanced features
static void demonstrateAdvanced() throws Exception {
System.out.println("\n=== ADVANCED FEATURES ===");
// Custom client configuration
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // Prefer HTTP/2
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/headers"))
.timeout(Duration.ofSeconds(5))
.header("User-Agent", "Java HTTP Client")
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
// Access response details
System.out.println("HTTP Version: " + response.version());
System.out.println("Status: " + response.statusCode());
response.headers().map().forEach((k, v) ->
System.out.println("Header: " + k + " = " + v));
}
}
// Real-world examples
class RealWorldHTTPExamples {
static HttpClient client = HttpClient.newHttpClient();
// REST API call
static String fetchUser(int userId) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/users/" + userId))
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
return response.body();
}
// Multiple async requests (parallel)
static void fetchMultiple() {
List<CompletableFuture<String>> futures = IntStream.rangeClosed(1, 5)
.mapToObj(i -> HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/users/" + i))
.build())
.map(req -> client.sendAsync(req, BodyHandlers.ofString())
.thenApply(HttpResponse::body))
.toList();
// Wait for all
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> System.out.println("All requests complete!"))
.join();
}
// POST JSON
static void createUser(String name, String email) throws Exception {
String json = String.format("""
{
"name": "%s",
"email": "%s"
}
""", name, email);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println("Created: " + response.body());
}
}5. The Comparison & Decision Layer
| Old (HttpURLConnection) | New (HTTP Client) |
|---|---|
| Verbose, low-level | Fluent builder API |
| HTTP/1.1 only | HTTP/2 support |
| Synchronous only | Async-first |
| Manual configuration | Sensible defaults |
| No timeout support | Built-in timeouts |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "send() vs sendAsync()?" Answer:
- send(): Blocks current thread until response
- sendAsync(): Returns CompletableFuture (non-blocking)
java
// send(): Blocks here!
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.body()); // Only runs after response
// sendAsync(): Doesn't block!
CompletableFuture<HttpResponse<String>> future =
client.sendAsync(request, BodyHandlers.ofString());
System.out.println("This runs immediately!"); // Doesn't wait
future.thenApply(HttpResponse::body)
.thenAccept(System.out::println);Pro-Tip: HTTP/2 multiplexing:
java
// HTTP/2 allows multiple requests over single connection!
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
// These can share connection (efficient!)
client.sendAsync(request1, BodyHandlers.ofString());
client.sendAsync(request2, BodyHandlers.ofString());
client.sendAsync(request3, BodyHandlers.ofString());