Stage 4: Modern Java, lesson 7 of 7

Calling APIs with the Java HTTP Client

Intermediate3 min read@since 16Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

Since Java 11, java.net.http.HttpClient is the standard way to call HTTP APIs, with no extra library.

  1. Build one client and reuse it: HttpClient.newBuilder().connectTimeout(…).
  2. Build a request: address, method, headers, body and timeout.
  3. Send it with send (blocking) or sendAsync (returns a CompletableFuture).
  4. Choose a body handler: ofString(), ofFile(path) or ofInputStream().

It uses HTTP/2 automatically, and HTTP/3 is available since Java 26. Always set timeouts and check the status code: a 404 or 500 is a normal response, not an exception.

In Spring apps, RestClient and HTTP interface clients add JSON mapping and error handling on top; in plain Java, pair HttpClient with Jackson for JSON.

Example

Java
HttpClient client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(5))
    .build();                                            // create once, reuse

HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.github.com/repos/openjdk/jdk"))
    .header("Accept", "application/json")
    .timeout(Duration.ofSeconds(10))
    .GET()
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
    System.out.println(response.body().substring(0, 80));
} else {
    System.out.println("Failed with status " + response.statusCode());
}

// Several calls in parallel
List<URI> uris = List.of(URI.create("https://example.com/a"), URI.create("https://example.com/b"));
List<CompletableFuture<String>> calls = uris.stream()
    .map(u -> client.sendAsync(HttpRequest.newBuilder(u).build(), HttpResponse.BodyHandlers.ofString())
                    .thenApply(HttpResponse::body))
    .toList();

Common mistake

Expecting an exception for a 404 or 500. HttpClient only throws for network problems and timeouts; always check statusCode().

Under the hood

HttpClient manages a connection pool, so creating one per request wastes connections and threads. With virtual threads, plain blocking send calls scale well and read more simply than long sendAsync chains. To POST JSON, use HttpRequest.BodyPublishers.ofString(json) with a Content-Type: application/json header.

Check yourself

When does HttpClient.send throw an exception?

How this connects

Where this leads

You've reached the end of this thread. Try a learning path for what's next.

Part of Upgrade from Java 8 to Java 25.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.