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.

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+ .

💡 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.

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.

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:

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.

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.

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.