The Java HTTP Client (java.net.http)
The java.net.http client is Java's modern, built-in way to make HTTP calls — a fluent HttpClient that supports synchronous and asynchronous requests, HTTP/2, headers, timeouts, and pluggable response handlers.
Learn The Java HTTP Client (java.net.http) in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Before You Start
You should be comfortable with exceptions (these calls throw checked ones), and it helps to have seen lambdas for the async CompletableFuture chains. The client ships in Java 11+ .
What You'll Learn
The Big Idea — Posting a Letter
💡 Analogy: An HttpRequest is an envelope: the uri is the address, the headers are the notes on the front (what's inside, who sent it), and the body is the letter. The HttpClient is the postal service: send means you wait at the post office for the reply, while sendAsync means you go home and get notified when it lands. A BodyHandler is your instruction for what to do with the reply — read it aloud ( ofString ), file it ( ofFile ), or shred it ( discarding ).
All the examples below talk to a tiny local server we start inside the same program on 127.0.0.1 with an OS-chosen free port — so they really run and print real output, with no internet needed.
1️⃣ A Synchronous Request
The simplest flow: create a client, build a request, and send it with a body handler. The example spins up a com.sun.net.httpserver.HttpServer on a random local port, then calls it — so it runs fully offline and prints the real status, a response header, and the body.
2️⃣ The Request Builder in Detail
HttpRequest.newBuilder() returns a fluent builder. Chain uri , one or more header calls, a timeout , and a method ( GET() , POST(publisher) , PUT(...) , DELETE() ), then build() . The result is immutable and reusable. You can build and inspect a request entirely offline:
3️⃣ Asynchronous with sendAsync
sendAsync returns a CompletableFuture instead of blocking. You can transform it with thenApply (here, pull out the body), chain further calls with thenCompose , and only block (via join / get ) when you finally need the value. Below we POST a body and the server echoes it back.
🧠 Quick Recall
Answer: no. send returns normally with a populated HttpResponse ; you must check response.statusCode() == 404 yourself. Only transport failures (no connection, timeout, bad host) throw.
Answer: a CompletableFuture<HttpResponse<T>> . You chain it with thenApply / thenAccept and block only when you call join() or get() .
Answer: HttpResponse.BodyHandlers.ofFile(path) . The response's body() then returns the Path it wrote to.
🎯 YOUR TURN — Uppercase service
Register a /shout endpoint that uppercases its text query parameter, then call it and print the result.
🧩 MINI-CHALLENGE — Handle status codes
Send two requests and branch on the status: 200 prints the body, anything else prints the error code. This is the everyday pattern for real APIs.
Common Errors
- ❌ Treating a 404/500 as a thrown error. It isn't. Fix: inspect response.statusCode() and branch.
- ❌ Forgetting the BodyHandler. send needs to know how to read the body. Fix: pass BodyHandlers.ofString() (or another handler).
- ❌ No timeout. A hung server can block forever. Fix: add .timeout(Duration.ofSeconds(n)) .
- ❌ Not handling the checked exceptions. send throws IOException and InterruptedException . Fix: declare or catch them.
- ❌ Expecting auto-JSON. The client moves bytes, not objects. Fix: serialise with Jackson/Gson, then pass the JSON string to ofString .
- ❌ Creating a new client per call. Wasteful. Fix: build one HttpClient and reuse it; it is thread-safe.
Pro Tips
- 💡 Reuse one client. HttpClient is immutable and thread-safe — share it across your app.
- 💡 Set an HTTP version with HttpClient.newBuilder().version(HttpClient.Version.HTTP_2) when you need it.
- 💡 Stream large responses with BodyHandlers.ofLines() or ofInputStream() instead of loading everything into memory.
- 💡 Pair with virtual threads so thousands of blocking send calls scale without async complexity.
📋 Quick Reference
Concept
Syntax
Note
client
HttpClient.newHttpClient()
Reuse it; thread-safe
request
HttpRequest.newBuilder()
Fluent, immutable result
uri
.uri(URI.create(url))
Target address
header
.header("Accept", "...")
Add one header
timeout
.timeout(Duration.ofSeconds(5))
Per-request limit
GET
.GET()
No body
POST
.POST(BodyPublishers.ofString(s))
Attach a body
send (sync)
client.send(req, handler)
Blocks for response
send (async)
client.sendAsync(req, handler)
CompletableFuture
body as text
BodyHandlers.ofString()
also ofFile / ofLines
status
response.statusCode()
int — check it yourself
header out
response.headers().firstValue("X")
Optional<String>
📚 Resources / Keep Learning
- 📘 HttpClient — Java 21 API documentation (docs.oracle.com)
- 📘 HttpRequest — Java 21 API documentation (docs.oracle.com)
- 📗 dev.java — official tutorials and guides
❓ Frequently Asked Questions
🎉 Lesson Complete!
You can now build requests with the fluent builder, send them synchronously or via sendAsync , pick the right BodyHandler , set headers and timeouts, and — crucially — check the status code instead of expecting an exception.
Next up: equals(), hashCode() & Object Methods — the contract every object you put in a HashSet or HashMap must obey.
Practice quiz
Which package holds Java's modern HTTP client?
- java.net
- java.io.http
- java.net.http
- javax.http
Answer: java.net.http. The modern client (HttpClient, HttpRequest, HttpResponse) lives in java.net.http, standardised in Java 11.
How do you create a default HttpClient?
- HttpClient.newHttpClient()
- new HttpClient()
- HttpClient.create()
- HttpClient.of()
Answer: HttpClient.newHttpClient(). HttpClient.newHttpClient() returns a client with sensible defaults; HttpClient.newBuilder() lets you configure one.
What does client.send(request, handler) do?
- Sends asynchronously
- Only builds the request
- Streams forever
- Sends synchronously and blocks for the response
Answer: Sends synchronously and blocks for the response. send(...) is the blocking call — it returns an HttpResponse once the server replies.
What does sendAsync return?
- An HttpResponse
- A CompletableFuture<HttpResponse<T>>
- void
- A String
Answer: A CompletableFuture<HttpResponse<T>>. sendAsync returns a CompletableFuture you can chain with thenApply/thenAccept and never block the calling thread.
Which BodyHandler reads the response body as text?
- BodyHandlers.ofString()
- BodyHandlers.ofText()
- BodyHandlers.asString()
- BodyHandlers.string()
Answer: BodyHandlers.ofString(). HttpResponse.BodyHandlers.ofString() collects the body into a String. There are also ofByteArray, ofFile, ofLines, etc.
How do you add a request header?
- .addHeader(...)
- .setHeader only after build
- .header("Accept", "application/json")
- .withHeader(...)
Answer: .header("Accept", "application/json"). The builder's .header(name, value) adds a header; .headers(...) adds several at once.
How do you set a per-request timeout?
- .setTimeout(5000)
- .timeout(Duration.ofSeconds(5))
- .wait(5)
- .deadline(5)
Answer: .timeout(Duration.ofSeconds(5)). The request builder takes a java.time.Duration via .timeout(...); exceeding it throws HttpTimeoutException.
Which method sends a request body (e.g. JSON)?
- .GET()
- .body(json)
- .send(json)
- .POST(BodyPublishers.ofString(json))
Answer: .POST(BodyPublishers.ofString(json)). Use .POST(HttpRequest.BodyPublishers.ofString(...)) (or .PUT) with a BodyPublisher to attach a request body.
How do you read a response header value?
- response.header("X")
- response.headers().firstValue("X")
- response.getHeader("X")
- response.h("X")
Answer: response.headers().firstValue("X"). response.headers() returns an HttpHeaders; firstValue(name) gives an Optional<String>.
What does response.statusCode() return for a successful GET?
- "OK"
- true
- 200
- 1
Answer: 200. statusCode() returns the numeric HTTP status as an int — 200 for success, 404 for not found, etc.
Continue this course
- Previous: Virtual Threads (Java 21)
- Next: equals(), hashCode() & Object Methods