Java Networking & HTTP Client

15 questions found

What is the modern java.net.http.HttpClient (introduced in Java 11), and how does it improve on the older HttpURLConnection for making HTTP requests?

Beginner
HttpClient provides a modern, fluent, builder-based API for constructing and sending HTTP requests (supporting HTTP/1.1 and HTTP/2, synchronous and asynchronous execution, and built-in support for common needs like redirects and timeouts) -- it significantly improves on the older HttpURLConnection, which was notoriously awkward and error-prone to use correctly (a confusing, low-level API originally designed decades ago, requiring careful manual handling of connection setup, streams, and error codes), making HttpClient the clearly recommended choice for any new Java code needing to make HTTP requests without pulling in a third-party library like Apache HttpClient or OkHttp.
HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/users"))
    .header("Accept", "application/json")
    .GET()
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
Real-world example A microservice calling an external REST API migrates from the older, awkward HttpURLConnection (requiring manual stream management and confusing connection state handling) to the modern HttpClient, immediately simplifying the code with its fluent builder API and built-in JSON-friendly string body handling.

Common follow-ups: Why did the JDK team decide to introduce an entirely new HTTP client rather than improving HttpURLConnection directly?;What third-party libraries (like Apache HttpClient or OkHttp) were commonly used to work around HttpURLConnection's limitations before Java 11?

RESTful Web APIs & Controllers;Serialization & Deserialization

How would you make an asynchronous, non-blocking HTTP request using HttpClient's sendAsync() method, and how does the returned CompletableFuture integrate with the rest of an asynchronous pipeline?

Intermediate
client.sendAsync(request, bodyHandler) immediately returns a CompletableFuture<HttpResponse<T>> without blocking the calling thread, letting you compose the eventual response using CompletableFuture's fluent chaining methods (thenApply(), thenAccept(), exceptionally()) to process the result once it arrives, integrating naturally with other asynchronous work in a larger reactive or CompletableFuture-based application pipeline rather than dedicating a thread to block waiting for each individual HTTP call to complete.
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/data"))
    .build();

CompletableFuture<String> futureBody = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .exceptionally(ex -> {
        System.err.println("Request failed: " + ex.getMessage());
        return "";
    });

futureBody.thenAccept(System.out::println);  // processes the result once it eventually arrives
Real-world example A dashboard service fetching data concurrently from five independent external APIs uses sendAsync() for all five requests simultaneously, combining their resulting CompletableFutures with CompletableFuture.allOf() to wait for all responses together, achieving far better overall latency than issuing five sequential blocking requests one after another.

Common follow-ups: How would you set a per-request timeout that's independent of the HttpClient's own default connection timeout?;What thread does the CompletableFuture's continuation callbacks (like thenApply) actually execute on by default?

Concurrency & Threads;Background Tasks & Hosted Services

How would you implement a custom BodySubscriber or BodyHandler to stream a very large HTTP response body incrementally, rather than buffering the entire response into memory at once?

Advanced
HttpResponse.BodyHandlers provides several built-in options beyond the common ofString()/ofByteArray() (which fully buffer the response body in memory before returning), including ofInputStream() (returns a standard InputStream you can read incrementally) and ofLines() (returns a Stream<String> of lines, lazily evaluated) -- for genuinely custom streaming processing needs, implementing your own BodySubscriber<T> gives full low-level control over how response body bytes are consumed as they arrive over the network, letting you process a very large response (like a multi-gigabyte file download) incrementally without ever holding the complete body in memory simultaneously.
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://example.com/large-file.zip"))
    .build();

// Stream the response body directly to a file, without buffering it fully in memory
HttpResponse<Path> response = client.send(request,
    HttpResponse.BodyHandlers.ofFile(Path.of("downloaded-file.zip")));

// Or process line-by-line for a large text response
HttpResponse<Stream<String>> lineResponse = client.send(request, HttpResponse.BodyHandlers.ofLines());
lineResponse.body().forEach(line -> processLine(line));
Real-world example A data-synchronization service downloading multi-gigabyte export files from a partner API uses HttpResponse.BodyHandlers.ofFile() to stream the response directly to disk incrementally, avoiding the out-of-memory risk that buffering the entire multi-gigabyte response into a byte array or String first would create.

Common follow-ups: What's the performance and memory trade-off of ofInputStream() versus a fully custom BodySubscriber implementation?;How would you implement progress reporting (bytes downloaded so far) for a large streaming download using a custom BodySubscriber?

File Uploads & Streaming Large Files;Diagnostics & Performance

How would you configure HttpClient with custom timeouts, a proxy, and connection pooling behavior, and what's the difference between a connection timeout and a request/response timeout?

Intermediate
HttpClient.newBuilder() lets you configure a connectTimeout (maximum time to establish the initial TCP/TLS connection) at the client level, while an individual HttpRequest.Builder's timeout() sets a per-request timeout (maximum time to wait for the complete request/response exchange) -- these are conceptually distinct: a connection timeout failure means the target server couldn't even be reached/connected to in time, while a request timeout failure means the connection succeeded but the server took too long to respond; HttpClient also supports configuring an explicit proxy (via ProxySelector) and internally manages connection pooling/reuse (particularly beneficial for HTTP/2's connection multiplexing) automatically without requiring explicit manual configuration for typical use cases.
HttpClient client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(5))   // max time to establish the connection itself
    .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
    .build();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/data"))
    .timeout(Duration.ofSeconds(10))          // max time for THIS specific request/response exchange
    .build();
Real-world example A service calling a historically slow, occasionally unresponsive downstream API configures both a short connectTimeout (5 seconds, since an unreachable server should fail fast) and a more generous per-request timeout (30 seconds, allowing genuinely slow-but-working responses time to complete), correctly distinguishing these two different failure modes with appropriately different tolerances.

Common follow-ups: What happens to an in-flight request if the configured timeout is exceeded -- what specific exception is thrown?;How does HttpClient's connection pooling and reuse behavior differ between HTTP/1.1 and HTTP/2 protocol versions?

HttpClient & Resilience (Polly);Diagnostics & Performance

How would you implement retry logic with exponential backoff for HTTP requests made via HttpClient, given the client doesn't provide built-in retry support natively?

Advanced
Since HttpClient doesn't include built-in retry logic, implementing resilient retry behavior requires wrapping request execution in your own retry loop (or using a dedicated resilience library like Resilience4j or Failsafe), catching transient failures (specific IOException subtypes, or specific HTTP status codes like 503 Service Unavailable) and retrying with an exponentially increasing delay between attempts (doubling the wait time after each failed attempt, often with added random jitter to avoid many clients retrying in lockstep and overwhelming a recovering server simultaneously), up to a maximum number of attempts before ultimately giving up and propagating the failure.
public <T> HttpResponse<T> sendWithRetry(HttpRequest request, HttpResponse.BodyHandler<T> handler, int maxAttempts) throws Exception {
    int attempt = 0;
    while (true) {
        try {
            HttpResponse<T> response = client.send(request, handler);
            if (response.statusCode() < 500) return response;  // don't retry client errors, only server errors
            throw new IOException("Server error: " + response.statusCode());
        } catch (IOException e) {
            attempt++;
            if (attempt >= maxAttempts) throw e;
            long delayMs = (long) (Math.pow(2, attempt) * 100);  // exponential backoff
            Thread.sleep(delayMs);
        }
    }
}
Real-world example A payment processing client retries a failed downstream API call up to three times with exponential backoff (200ms, then 400ms, then 800ms delays) specifically for 5xx server errors and network-level IOExceptions, while deliberately NOT retrying 4xx client errors (like an invalid request), since retrying a genuinely malformed request would just fail identically every time.

Common follow-ups: Why is it important to add random jitter to exponential backoff delays in a system with many concurrent clients, rather than using purely deterministic delay values?;What HTTP status codes and exception types genuinely warrant a retry versus those that don't?

HttpClient & Resilience (Polly);Rate Limiting

How would you send a POST request with a JSON request body using HttpClient, and correctly parse a JSON response using a library like Jackson?

Intermediate
HttpRequest.BodyPublishers.ofString(jsonString) constructs the request body, combined with setting the appropriate Content-Type: application/json header, sends a JSON payload; on the response side, HttpResponse.BodyHandlers.ofString() retrieves the raw JSON response body as a String, which you then deserialize into a Java object using a JSON library like Jackson's ObjectMapper, since HttpClient itself has no built-in JSON serialization/deserialization capability -- it deliberately operates purely at the HTTP transport level, leaving content-format handling to a separate, dedicated library.
ObjectMapper mapper = new ObjectMapper();
String jsonBody = mapper.writeValueAsString(new CreateUserRequest("Alice", "alice@example.com"));

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/users"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
User createdUser = mapper.readValue(response.body(), User.class);
Real-world example A client library calling a REST API serializes a Java DTO to JSON via Jackson before constructing the HttpRequest's body, then deserializes the JSON response back into a typed Java object after receiving it, cleanly separating HttpClient's pure HTTP transport responsibilities from Jackson's dedicated JSON serialization responsibilities.

Common follow-ups: Why did the JDK team deliberately choose NOT to include built-in JSON support directly in HttpClient?;How would you handle a non-2xx response status code gracefully when parsing the response body, given an error response might have a different JSON structure than a success response?

RESTful Web APIs & Controllers;Serialization & Deserialization

How would you implement a WebSocket client using java.net.http.WebSocket for real-time, bidirectional communication, and how does its event-driven Listener interface work?

Advanced
HttpClient.newWebSocketBuilder().buildAsync(uri, listener) establishes a WebSocket connection, with a custom WebSocket.Listener implementation receiving callback invocations for connection lifecycle events (onOpen), incoming text/binary messages (onText/onBinary), and connection closure (onClose) or errors (onError) -- sending messages uses the resulting WebSocket instance's sendText()/sendBinary() methods (themselves returning a CompletableFuture indicating when the send completes), making this a fully asynchronous, callback-driven API well suited to real-time bidirectional communication scenarios like a chat client or live data feed subscriber.
WebSocket.Listener listener = new WebSocket.Listener() {
    public void onOpen(WebSocket webSocket) {
        System.out.println("Connected");
        WebSocket.Listener.super.onOpen(webSocket);
    }
    public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
        System.out.println("Received: " + data);
        return WebSocket.Listener.super.onText(webSocket, data, last);
    }
};

WebSocket webSocket = client.newWebSocketBuilder()
    .buildAsync(URI.create("wss://echo.example.com"), listener)
    .join();
webSocket.sendText("Hello!", true);
Real-world example A real-time price-monitoring client connects to a financial data provider's WebSocket feed using java.net.http.WebSocket, receiving continuous price update messages via the onText() callback and reacting to each one immediately, without needing a separate third-party WebSocket library dependency.

Common follow-ups: Why must Listener implementations typically call the super method (WebSocket.Listener.super.onText()) at the end of their own overrides -- what does the default implementation do?;How would you implement automatic reconnection logic if the WebSocket connection drops unexpectedly?

SignalR & Real-Time Communication;Background Tasks & Hosted Services

What is the java.net.Socket and ServerSocket class pair, and how would you implement a basic TCP client-server communication using them at a lower level than HttpClient?

Intermediate
Socket represents a client-side TCP connection endpoint, and ServerSocket represents a server-side listener that accepts incoming client connections (accept() blocks until a client connects, returning a Socket representing that specific connection) -- both provide raw InputStream/OutputStream access for reading/writing bytes directly over the established TCP connection, representing a much lower-level networking API than HttpClient (which builds an entire HTTP protocol layer on top of raw sockets), appropriate when you need to implement a custom, non-HTTP protocol or need direct low-level control over the TCP connection itself.
// Server
ServerSocket serverSocket = new ServerSocket(8080);
Socket clientSocket = serverSocket.accept();  // blocks until a client connects
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String message = in.readLine();

// Client
Socket socket = new Socket("localhost", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello, server!");
Real-world example A custom, lightweight internal protocol between two in-house microservices (not needing full HTTP semantics) is implemented directly using raw Socket/ServerSocket, avoiding the overhead of a full HTTP request/response cycle for a very simple, high-frequency, low-latency internal communication need.

Common follow-ups: Why would you choose raw sockets over HttpClient or gRPC for a custom protocol, given both provide more built-in structure?;How does this classic blocking Socket API relate to NIO's non-blocking SocketChannel discussed earlier?

gRPC Services;I/O & NIO

How would you configure HttpClient to trust a custom or self-signed SSL/TLS certificate for testing against an internal service, and what security risks does disabling certificate validation entirely introduce?

Advanced
HttpClient.newBuilder().sslContext(customSslContext) lets you supply a custom SSLContext configured with a TrustManager trusting your specific self-signed certificate (or an entire custom certificate authority), the correct, security-conscious approach for legitimately trusting a specific known certificate; a common but dangerous shortcut sometimes seen in test code involves an SSLContext configured with a TrustManager that accepts ANY certificate unconditionally, which -- as covered earlier regarding HttpClient's outbound certificate validation -- completely defeats TLS's security guarantees and must never be used in production code, since it makes the connection vulnerable to man-in-the-middle interception despite superficially appearing to use HTTPS.
// Correct approach: trust a SPECIFIC known certificate via a properly configured TrustManager
KeyStore trustStore = KeyStore.getInstance("PKCS12");
trustStore.load(new FileInputStream("custom-truststore.p12"), "password".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);

HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
Real-world example An integration test suite calling an internal service secured with a self-signed certificate (issued by the company's own internal CA, not a public one) configures HttpClient with a custom SSLContext trusting specifically that internal CA's certificate, rather than resorting to the dangerous, blanket trust-everything shortcut that would silently defeat TLS validation entirely.

Common follow-ups: What's the specific difference in risk between trusting one known self-signed certificate versus disabling certificate validation entirely?;How would you generate and configure a proper internal certificate authority for a company's internal services rather than relying on self-signed certificates per-service?

HTTPS Certificates & Transport Security;Security Headers Antiforgery & CSRF Protection

How would you send URL-encoded form data (application/x-www-form-urlencoded) using HttpClient, as opposed to a JSON request body?

Intermediate
Form-encoded data requires manually constructing the properly URL-encoded key=value&key2=value2 formatted string (using URLEncoder.encode() to correctly escape special characters in each individual key/value pair) and setting the Content-Type: application/x-www-form-urlencoded header explicitly, since HttpClient itself has no built-in form-encoding helper the way some other HTTP libraries provide -- this is commonly needed when interacting with older APIs or OAuth token endpoints that specifically expect this traditional form-encoded format rather than JSON.
Map<String, String> formData = Map.of("grant_type", "client_credentials", "client_id", "abc123");
String encodedBody = formData.entrySet().stream()
    .map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
    .collect(Collectors.joining("&"));

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://auth.example.com/oauth/token"))
    .header("Content-Type", "application/x-www-form-urlencoded")
    .POST(HttpRequest.BodyPublishers.ofString(encodedBody))
    .build();
Real-world example An OAuth2 client requesting an access token from an authorization server manually constructs a URL-encoded form body (since OAuth2 token endpoints conventionally expect this format rather than JSON), correctly escaping each parameter value via URLEncoder to handle any special characters safely.

Common follow-ups: Why do OAuth2 and many older web APIs specifically use form encoding rather than JSON for these particular requests?;What specific characters does URLEncoder.encode() escape, and why is manual string concatenation without encoding dangerous?

Authentication;RESTful Web APIs & Controllers

Showing 1–10 of 15