Card 05/ 09

ExampleDifficulty: Intermediate1 min

Builder in the Wild: Java's HttpRequest.newBuilder()

URI, headers, timeout and method are set one call at a time, then build() returns an immutable request.

Java's HTTP client, in java.net.http since Java 11, creates every request through a builder. HttpRequest.newBuilder() returns an HttpRequest.Builder, each call sets one part of the request, and build() returns an HttpRequest that has no setters at all. The listing builds a request without sending it, so it runs with no network.

java
import java.net.URI;
import java.net.http.HttpRequest;
import java.time.Duration;

public class BdHttpRequest {
    public static void main(String[] args) {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/orders"))
                .header("Content-Type", "application/json")
                .timeout(Duration.ofSeconds(5))
                .POST(HttpRequest.BodyPublishers.ofString("{\"id\":\"A-1042\"}"))
                .build();

        System.out.println(request);
        System.out.println("method:  " + request.method());
        System.out.println("headers: " + request.headers().map());
        System.out.println("timeout: " + request.timeout().orElseThrow());

        try {
            HttpRequest.newBuilder().header("Accept", "text/plain").build();
        } catch (IllegalStateException error) {
            System.out.println("refused: " + error.getMessage());
        }
    }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
https://api.example.com/orders POST
method:  POST
headers: {Content-Type=[application/json]}
timeout: PT5S
refused: uri is null
A POST request built one part at a time, and a builder with no URI that `build()` refuses.

Every part is optional in the builder's calls and checked in build(). The method defaults to GET, the headers to none, and the timeout to none, so a request names only what differs. The URI has no default, and the second build() throws an IllegalStateException whose message, from OpenJDK 21, is uri is null.

What this case shares with the report is the shape that identifies the pattern: calls that each set one named part, often chained, and one final call that validates and returns the finished object. The JDK adds one thing the report does not need: the HttpRequest it returns is immutable, so a request handed to several threads cannot be changed by any of them. That is the strongest reason to use a builder in Java, where a class without setters has no other way to receive a dozen optional parts by name.