Definition
- HttpClient.newHttpClient() creates a default client. Use
HttpClient.newBuilder()for custom configuration (timeouts, redirect policy, executor). - send(request, BodyHandlers.ofString()) is synchronous: blocks the calling thread until the response arrives.
- sendAsync(request, BodyHandlers.ofString()) is asynchronous: returns
CompletableFutureimmediately.> - HTTP/2 is supported by default with automatic fallback to HTTP/1.1.
- The client is immutable and thread-safe: designed to be created once and reused.
- Replaces
HttpURLConnectionwhich required manual InputStream/OutputStream handling, had no HTTP/2, and no async support.
HttpURLConnection is like sending a bank letter by hand: open envelope, write address, stamp, wait at the post office. HttpClient is like the bank's digital messaging system: type message, click send-and-wait (sync) or click send-and-continue (async). It supports modern protocols (HTTP/2 for multiplexed connections) and connection reuse.
Understanding the Concepts
How do you make a synchronous GET request?
package com.bank.gateway;
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
public class HttpClientDemo {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.bank.example/accounts/SAV2001"))
.header("Authorization", "Bearer token123")
.GET()
.build();
// Synchronous: blocks until response
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}
}
How do you make an asynchronous request?
// Asynchronous: returns CompletableFuture immediately
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(body -> System.out.println("Async body: " + body))
.join(); // wait for completion in demo
How do you make a POST request?
String json = "{\"holder\":\"Rajesh\",\"amount\":50000}";
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.bank.example/transfers"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.timeout(Duration.ofSeconds(30))
.build();
HttpResponse<String> postResponse = client.send(
postRequest, HttpResponse.BodyHandlers.ofString());
How is HttpClient better than HttpURLConnection?
| Feature | HttpURLConnection | HttpClient (Java 11) |
|---------|------------------|---------------------|
| API style | Verbose, manual streams | Clean builder pattern |
| HTTP/2 | Not supported | Built-in |
| Async | Not supported | sendAsync + CompletableFuture |
| Connection pooling | Manual | Automatic |
| Thread safety | Not reusable | Immutable, thread-safe |
| Body handling | Manual InputStream | Typed BodyHandlers |
What are common mistakes?
WRONG: Creating a new HttpClient per request.
// WRONG: wasteful -- creates new client each time
HttpClient bad = HttpClient.newHttpClient();
HttpResponse<String> resp = bad.send(req, BodyHandlers.ofString());
CORRECT: Reuse a single client instance.
// CORRECT: create once, reuse everywhere
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
What should a senior engineer know?
- HttpClient uses a shared executor (ForkJoinPool.commonPool) for async operations by default. For production, provide a custom executor via
HttpClient.newBuilder().executor(myExecutor). - For Spring applications,
WebClientorRestClientare typically used instead of raw HttpClient, but HttpClient is the standard library option with zero dependencies. - HttpClient supports WebSocket via
HttpClient.newWebSocketBuilder(). - The client handles redirects, authentication, and cookie management via builder configuration.
Visual Overview
Synchronous (send): ┌──────────┐ request ┌──────────┐ │ Client │──────────────→│ Server │ │ (blocks) │←──────────────│ │ └──────────┘ response └──────────┘ Asynchronous (sendAsync): ┌──────────┐ request ┌──────────┐ │ Client │──────────────→│ Server │ │ (returns │ │ │ │ Future) │ │ │ │ ... │←──────────────│ │ │ callback │ response └──────────┘ └──────────┘
Key Takeaway
Java 11's HttpClient replaces HttpURLConnection with a modern API: builder pattern for clients and requests, HTTP/2 support, synchronous send() that blocks and returns HttpResponse, and asynchronous sendAsync() that returns CompletableFuture. The client is immutable and thread-safe, designed to be created once and reused.
At National Bank of India, the payment gateway uses synchronous send() for single transfers and sendAsync() to fan out balance checks to multiple account services in parallel.
In interviews, mention the three key improvements: HTTP/2 support, async via CompletableFuture, and a clean builder API.
Remember one sentence: HttpClient is the modern, reusable, HTTP/2-capable replacement for HttpURLConnection with both sync and async modes.
---