HttpClient.newHttpClient()
Create an HttpClient with default settings in one call, and know exactly which defaults (no redirects, no timeouts) you are accepting.
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
httpsthe version is agreed during the TLS handshake. Over plainhttpthe client asks for an upgrade withUpgrade: h2cand 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.proxyHostandhttps.proxyHostsystem properties), the defaultSSLContext, 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
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.
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: 3The 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.
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/ordersA 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.
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