HttpClient.newHttpClient()

Create an HttpClient with default settings in one call, and know exactly which defaults (no redirects, no timeouts) you are accepting.

Signature
static HttpClient newHttpClient()
  • Added in Java Java 11

Return value

HttpClientA new HttpClient with default settings. It is equivalent to HttpClient.newBuilder().build().

Errors

UncheckedIOException if the I/O resources the client needs cannot be allocated.

Changelog

  • PHP Java 21HttpClient implements AutoCloseable and gains close(), shutdown(), shutdownNow(), awaitTermination(Duration) and isTerminated().

HttpClient.newHttpClient() returns a client with every setting at its default. It is exactly HttpClient.newBuilder().build(), so switch to the builder the moment you need to change anything.

What the defaults are

  • Version preference HTTP/2. Over https the version is agreed during the TLS handshake. Over plain http the client asks for an upgrade with Upgrade: h2c and carries on in HTTP/1.1 when the server ignores it.
  • Redirect policy NEVER: a 301 or 302 comes back to you as the response.
  • No connect timeout and no request timeout.
  • The default proxy selector (which reads the http.proxyHost and https.proxyHost system properties), the default SSLContext, no authenticator and no cookie handler.
  • An executor the client creates for its own asynchronous work.

One client, many requests

A client owns a connection pool, a selector thread and that executor. It is immutable and safe to share between threads. Create one when the service starts, keep it in a field, and send every call through it.

The common mistake is calling newHttpClient() inside the method that makes the request. Each new client starts with an empty pool, so every call pays for a fresh TCP connection and, over HTTPS, a fresh TLS handshake. Under load that is slower and leaves threads behind until the old clients are collected.

Closing

Since Java 21 HttpClient implements AutoCloseable. close() lets requests already in flight finish and then releases threads and connections; shutdownNow() aborts them instead. A long-running service closes its client on shutdown. Short programs and tests can use try-with-resources. A closed client refuses new work: send throws an IOException.

When the defaults are wrong

For calls between services the missing timeouts are the default that hurts. A dependency that accepts the connection and then stalls holds your thread until your own callers give up, and the stuck threads pile up. In production use newBuilder() to set a connect timeout, and set timeout() on each request. Keep newHttpClient() for scripts, tests and tools where waiting is harmless.

The redirect default surprises people too. An API that moved to a new URL answers 301 with an empty body, and code that only reads the body sees an empty string instead of an error.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Calling a health endpoint from a command-line tool or a smoke test where waiting is harmless.
  • Holding one shared client in a service class that every request handler uses to call a downstream API.
  • Closing the shared client from a shutdown hook so its threads and connections are released cleanly.

Frequently asked questions

Should I create a new HttpClient for each request in Java?

No. A client is immutable and thread-safe and keeps a pool of connections, so create one at startup and share it. A client per request opens a new connection, and over HTTPS performs a new TLS handshake, on every call.

What is the default timeout of Java's HttpClient?

There is none. newHttpClient() sets no connect timeout and requests have no timeout unless you call timeout() on the request builder. Use HttpClient.newBuilder().connectTimeout(...) and HttpRequest.Builder.timeout(...) for calls to other services.

Does HttpClient.newHttpClient() follow redirects?

No. Its redirect policy is HttpClient.Redirect.NEVER, so a 301 or 302 is returned as the response. Build the client with followRedirects(HttpClient.Redirect.NORMAL) to follow them, except from HTTPS to HTTP.

More examples

One shared client reuses its connection

The server counts distinct client ports, one per TCP connection. The shared client sends all three requests over one pooled connection; a new client per call opens a new one every time, which over HTTPS also means a new TLS handshake.

Example · java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

// Writes a small text response; an empty body is sent with no body at all.
void reply(HttpExchange exchange, int status, String contentType, String body) throws IOException {
    byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
    if (contentType != null) {
        exchange.getResponseHeaders().set("Content-Type", contentType);
    }
    exchange.sendResponseHeaders(status, bytes.length == 0 ? -1 : bytes.length);
    try (OutputStream out = exchange.getResponseBody()) {
        out.write(bytes);
    }
}

// Calls the stock service through whatever client it is given.
int stock(HttpClient client, String base, String sku) throws Exception {
    HttpRequest request = HttpRequest.newBuilder(URI.create(base + "/stock/" + sku)).build();
    return Integer.parseInt(client.send(request, HttpResponse.BodyHandlers.ofString()).body());
}

void main() throws Exception {
    Set<Integer> clientPorts = ConcurrentHashMap.newKeySet();
    HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
    server.createContext("/stock/", exchange -> {
        // Each TCP connection arrives from its own client-side port.
        clientPorts.add(exchange.getRemoteAddress().getPort());
        reply(exchange, 200, "text/plain", "7");
    });
    server.start();
    String base = "http://127.0.0.1:" + server.getAddress().getPort();
    List<String> skus = List.of("A-1", "B-2", "C-3");

    try (HttpClient shared = HttpClient.newHttpClient()) {
        for (String sku : skus) {
            stock(shared, base, sku);
        }
    }
    IO.println("shared client, connections: " + clientPorts.size());

    clientPorts.clear();
    for (String sku : skus) {
        try (HttpClient perCall = HttpClient.newHttpClient()) {
            stock(perCall, base, sku);
        }
    }
    IO.println("client per call, connections: " + clientPorts.size());
    server.stop(0);
}

// Output:
// shared client, connections: 1
// client per call, connections: 3

The default client does not follow redirects

With the default <code>Redirect.NEVER</code> policy the 301 is the response, and its body is empty. Code that only reads <code>body()</code> would treat this as an empty order list, which is why checking <code>statusCode()</code> first matters.

Example · java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

// Writes a small text response; an empty body is sent with no body at all.
void reply(HttpExchange exchange, int status, String contentType, String body) throws IOException {
    byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
    if (contentType != null) {
        exchange.getResponseHeaders().set("Content-Type", contentType);
    }
    exchange.sendResponseHeaders(status, bytes.length == 0 ? -1 : bytes.length);
    try (OutputStream out = exchange.getResponseBody()) {
        out.write(bytes);
    }
}

void main() throws Exception {
    HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
    server.createContext("/v1/orders", exchange -> {
        exchange.getResponseHeaders().set("Location", "/v2/orders");
        reply(exchange, 301, null, "");
    });
    server.createContext("/v2/orders", exchange -> reply(exchange, 200, "application/json", "[]"));
    server.start();
    URI uri = URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/v1/orders");

    try (HttpClient client = HttpClient.newHttpClient()) {
        HttpResponse<String> response = client.send(HttpRequest.newBuilder(uri).build(),
                HttpResponse.BodyHandlers.ofString());
        IO.println("status: " + response.statusCode());
        IO.println("body: [" + response.body() + "]");
        IO.println("location: " + response.headers().firstValue("Location").orElse("-"));
    } finally {
        server.stop(0);
    }
}

// Output:
// status: 301
// body: []
// location: /v2/orders

A closed client refuses new requests

<code>try (client)</code> closes an existing variable at the end of the block. <code>close()</code> waited for the first request and released the client's threads, so the second <code>send</code> fails immediately; in a service, close the shared client only during shutdown.

Example · java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

// Writes a small text response; an empty body is sent with no body at all.
void reply(HttpExchange exchange, int status, String contentType, String body) throws IOException {
    byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
    if (contentType != null) {
        exchange.getResponseHeaders().set("Content-Type", contentType);
    }
    exchange.sendResponseHeaders(status, bytes.length == 0 ? -1 : bytes.length);
    try (OutputStream out = exchange.getResponseBody()) {
        out.write(bytes);
    }
}

void main() throws Exception {
    HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
    server.createContext("/ping", exchange -> reply(exchange, 200, "text/plain", "pong"));
    server.start();
    HttpRequest ping = HttpRequest.newBuilder(
            URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/ping")).build();

    HttpClient client = HttpClient.newHttpClient();
    try (client) {
        IO.println(client.send(ping, HttpResponse.BodyHandlers.ofString()).body());
    }
    IO.println("terminated: " + client.isTerminated());

    try {
        client.send(ping, HttpResponse.BodyHandlers.ofString());
    } catch (IOException e) {
        IO.println(e.getClass().getSimpleName() + ": " + e.getMessage());
    } finally {
        server.stop(0);
    }
}

// Output:
// pong
// terminated: true
// IOException: closed

Discussion

  • Be the first to comment on this lesson.