HttpRequest.newBuilder()
Build an immutable HttpRequest with its URI, method, headers and per-request timeout, and avoid the headers and URIs the builder rejects.
static HttpRequest.Builder newBuilder(URI uri)- Added in Java Java 11
Parameters
| Parameter | Type | Description |
|---|---|---|
$uri | URI | The request URI. Its scheme must be http or https. |
Return value
HttpRequest.BuilderA new request builder with the URI set and the method defaulting to GET. Overloads: newBuilder() with no URI, and newBuilder(HttpRequest request, BiPredicate<String,String> filter) to start from an existing request.
Errors
IllegalArgumentException if the URI scheme is not http or https; NullPointerException if uri is null. Builder methods throw IllegalArgumentException for restricted or invalid headers and non-positive timeouts, and build() throws IllegalStateException when no URI was set.
Changelog
- PHP Java 16Added the overload newBuilder(HttpRequest request, BiPredicate<String,String> filter), which copies a request and keeps only the headers the filter accepts.
- PHP Java 18HttpRequest.Builder gains HEAD().
HttpRequest.newBuilder(uri) starts a request. The builder collects the method, headers, body and timeout, and build() returns an immutable HttpRequest that can be sent any number of times, from any thread.
Methods
The method defaults to GET. POST(body), PUT(body), DELETE() and HEAD() (Java 18) cover the usual ones; method("PATCH", body) sends anything else. CONNECT is rejected. Methods that take a body need a BodyPublisher, such as BodyPublishers.ofString(json) or BodyPublishers.noBody().
Headers
header(name, value) adds a value, so calling it twice sends the header twice. setHeader(name, value) replaces any earlier value, and headers("A", "1", "B", "2") adds pairs. Names and values are validated as you add them: a value containing a line break throws IllegalArgumentException, which blocks header injection from user input.
A few headers belong to the client and cannot be set: Connection, Content-Length, Expect, Host and Upgrade. Content-Length comes from the body publisher and Host from the URI. Trying to set one throws restricted header name at the header() call, which surprises people porting code from other clients.
Timeouts
timeout(Duration) sets how long send waits for the response status and headers before throwing HttpTimeoutException. There is no default, and it is the setting that protects a service from a stalled dependency, so set it on every request to another service. It must be positive. In Java 25 it stops counting once the headers arrive, so a body that trickles in slowly is not cut off by it; bound that with orTimeout on sendAsync or a size cap.
The URI
Only http and https are accepted. URI.create throws on illegal characters such as a space, so encode path segments and query values with URLEncoder.encode(value, StandardCharsets.UTF_8) before joining them into a string.
Reusing and copying
Because a built request is immutable, a request with fixed headers can live in a field and be reused. To vary one, copy() a builder, or use HttpRequest.newBuilder(request, filter) (Java 16) to start from a built request with only the headers the filter keeps. That is handy for logging or retrying a request without its Authorization header.
Example
When to use it
- Calling a partner API with an Accept header, a bearer token and a five-second timeout on every request.
- Building a search URL from user input with URLEncoder so spaces and ampersands cannot break the query.
- Logging the exact outgoing request in an audit trail with the Authorization header filtered out.
Frequently asked questions
How do I add an Authorization header to a Java HttpClient request?
Set it on the request: HttpRequest.newBuilder(uri).header("Authorization", "Bearer " + token).build(). For basic authentication, Base64-encode user:password and send "Basic " plus the result, or configure an Authenticator on the client builder.
Why does HttpRequest.Builder throw restricted header name?
Connection, Content-Length, Expect, Host and Upgrade are managed by the client and cannot be set on a request. The client derives Host from the URI and Content-Length from the body publisher.
How do I send a PATCH request with Java HttpClient?
Use method("PATCH", HttpRequest.BodyPublishers.ofString(json)) on the request builder and set a Content-Type header. The builder has shortcuts only for GET, POST, PUT, DELETE and HEAD.
More examples
Headers and URIs the builder rejects
These checks happen while the request is being built, before anything touches the network. The client sets <code>Host</code> from the URI and <code>Content-Length</code> from the body publisher, so there is nothing to set by hand.
import java.net.http.HttpRequest;
void main() {
URI uri = URI.create("http://localhost/orders");
for (String name : List.of("Host", "Content-Length", "Connection", "X-Request-Id")) {
try {
HttpRequest.newBuilder(uri).header(name, "1");
IO.println(name + ": accepted");
} catch (IllegalArgumentException e) {
IO.println(name + ": " + e.getMessage());
}
}
try {
HttpRequest.newBuilder(URI.create("ftp://localhost/file"));
} catch (IllegalArgumentException e) {
IO.println(e.getMessage());
}
try {
HttpRequest.newBuilder().build();
} catch (IllegalStateException e) {
IO.println(e.getMessage());
}
}
// Output:
// Host: restricted header name: "Host"
// Content-Length: restricted header name: "Content-Length"
// Connection: restricted header name: "Connection"
// X-Request-Id: accepted
// invalid URI scheme ftp
// uri is nullCopying a request without its credentials
<code>setHeader</code> replaced <code>r-1</code> with <code>r-2</code> instead of adding a second value. The filter receives each header name and value and keeps the ones it returns <code>true</code> for, which makes a safe copy for logs, or a retry against a host that must not see the token.
import java.net.http.HttpRequest;
void main() {
HttpRequest original = HttpRequest.newBuilder(URI.create("https://api.example.com/v1/reports"))
.header("Authorization", "Bearer test-token-123")
.header("Accept", "application/json")
.setHeader("X-Request-Id", "r-1")
.setHeader("X-Request-Id", "r-2")
.build();
IO.println("original: " + original.headers().map());
// Java 16: keep every header except Authorization.
HttpRequest forLog = HttpRequest.newBuilder(original, (name, value) -> !name.equalsIgnoreCase("Authorization"))
.build();
IO.println("for log: " + forLog.method() + " " + forLog.uri() + " " + forLog.headers().map());
}
// Output:
// original: {Accept=[application/json], Authorization=[Bearer test-token-123], X-Request-Id=[r-2]}
// for log: GET https://api.example.com/v1/reports {Accept=[application/json], X-Request-Id=[r-2]}DELETE, HEAD and PATCH
There is no <code>PATCH()</code> shortcut, so <code>method("PATCH", publisher)</code> is the way to send one. <code>HEAD()</code> arrived in Java 18; before that the same request was written as <code>method("HEAD", BodyPublishers.noBody())</code>.
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("/items/42", exchange -> {
String method = exchange.getRequestMethod();
String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
switch (method) {
case "DELETE", "HEAD" -> reply(exchange, 204, null, "");
case "PATCH" -> reply(exchange, 200, "application/json", "patched with " + body);
default -> reply(exchange, 405, null, "");
}
});
server.start();
URI uri = URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/items/42");
List<HttpRequest> requests = List.of(
HttpRequest.newBuilder(uri).DELETE().build(),
HttpRequest.newBuilder(uri).HEAD().build(),
HttpRequest.newBuilder(uri)
.method("PATCH", HttpRequest.BodyPublishers.ofString("{\"qty\":3}"))
.header("Content-Type", "application/merge-patch+json")
.build(),
HttpRequest.newBuilder(uri).build());
try (HttpClient client = HttpClient.newHttpClient()) {
for (HttpRequest request : requests) {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body().isEmpty() ? "" : " " + response.body();
IO.println(request.method() + " -> " + response.statusCode() + body);
}
} finally {
server.stop(0);
}
}
// Output:
// DELETE -> 204
// HEAD -> 204
// PATCH -> 200 patched with {"qty":3}
// GET -> 405
Discussion