Java Network Programming: HTTP, Sockets and Timeouts - Yenra

Run local HTTP and TCP examples, distinguish connection and read timeouts, and investigate network failures by layer.

Two navy communication stations exchange amber packets along opposite teal paths beside an unnumbered clock disk.
Conceptual illustration: a request and its response need a protocol, a destination and a waiting policy.

Use an HTTP client when the other service speaks HTTP; use sockets when you own a lower-level protocol or must implement one. In either case, define how messages end, how much data is acceptable and when waiting should stop. These local Java examples make those decisions visible before external networking complicates the picture.

Start with a loopback lab

The download contains HttpDemo.java and SocketDemo.java, with instructions. Both were compiled and run with Java 25. Each opens its own listener on 127.0.0.1 using an operating-system-assigned port, exchanges a small synthetic message and closes its resources before exit. No external service or account is required.

Download both networking examples and README (ZIP)

javac HttpDemo.java SocketDemo.java
java HttpDemo
java SocketDemo
Status: 200
Body: ready
Echo: PING

The loopback HTTP connection intentionally uses plain HTTP for a contained exercise. Real network traffic carrying credentials or sensitive content should use the service's supported TLS configuration. The security guide covers trust and hostname checks.

Build an HTTP request with explicit policy

import com.sun.net.httpserver.HttpServer;
import java.net.*;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

public class HttpDemo {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
        server.createContext("/health", exchange -> {
            try (exchange) {
                byte[] body = "ready\n".getBytes(StandardCharsets.UTF_8);
                exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
                exchange.sendResponseHeaders(200, body.length);
                exchange.getResponseBody().write(body);
            }
        });
        server.start();
        try (HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(2))
                .followRedirects(HttpClient.Redirect.NEVER).build()) {
            URI uri = URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/health");
            HttpRequest request = HttpRequest.newBuilder(uri)
                    .timeout(Duration.ofSeconds(5)).GET().build();
            HttpResponse<String> response = client.send(request,
                    HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
            System.out.println("Status: " + response.statusCode());
            System.out.print("Body: " + response.body());
            if (response.statusCode()!=200 || !response.body().equals("ready\n"))
                throw new AssertionError("Unexpected local response");
        } finally {
            server.stop(0);
        }
    }
}

The HttpServer is a tiny local fixture. The application-facing part is the client: build it once for related requests, create a request, send it with a body handler and inspect the response status. HttpClient can reuse connections, so creating one client for every request often defeats useful reuse. Its API documentation explains the client lifetime and response-handling choices.

A 404 or 500 is an HTTP response and normally arrives as a response object; decide what that status means for the operation. An I/O exception indicates a different failure path. This example requires status 200 and exactly ready followed by a newline, then asserts those facts.

ofString stores the body in memory, which is appropriate for this trusted tiny fixture. For external responses, enforce an acceptable body size, verify content type and encoding, and choose a bounded subscriber or controlled streaming design. Streaming also needs a policy for body consumption and closure. A successful header response alone does not prove that the entire body has been read.

Distinguish timeouts from a complete deadline

The client sets a two-second connection timeout and the request sets a five-second timeout. A connection timeout matters when establishing a new connection; an already reused connection has different behavior. A request timeout bounds the relevant HTTP operation under its API contract. Treat streaming-body consumption separately when selecting a handler. See connectTimeout and request timeout.

A whole user operation may include DNS, several calls, retries and body processing. Record one overall deadline and pass the remaining allowance into each step. Five retries with a five-second allowance each can exceed the user's intended wait by a wide margin.

Retry only when the operation's semantics make it safe, with a bounded count, backoff and the remaining deadline. A timeout after sending a write does not establish whether the server applied it. Use the service's idempotency mechanism or reconcile the outcome before repeating a consequential change. The example disables automatic redirects so destination changes stay explicit.

Give a socket protocol a frame

TCP delivers an ordered byte stream. A sender's write does not define a message boundary for the receiver. A protocol must supply one: fixed length, a length prefix or a delimiter with an escaping rule. SocketDemo uses exactly four ASCII bytes, PING, and echoes those four bytes back.

import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;

public class SocketDemo {
    public static void main(String[] args) throws Exception {
        try (ServerSocket listener = new ServerSocket()) {
            listener.bind(new InetSocketAddress("127.0.0.1", 0));
            listener.setSoTimeout(3000);
            try (ExecutorService worker = Executors.newSingleThreadExecutor()) {
                Future<?> served = worker.submit(() -> {
                    try (Socket peer = listener.accept()) {
                        peer.setSoTimeout(3000);
                        byte[] bytes = peer.getInputStream().readNBytes(4);
                        if (bytes.length != 4) throw new IllegalStateException("Short message");
                        peer.getOutputStream().write(bytes);
                        peer.getOutputStream().flush();
                    } catch (Exception e) { throw new RuntimeException(e); }
                });
                try (Socket client = new Socket()) {
                    client.connect(new InetSocketAddress("127.0.0.1", listener.getLocalPort()), 2000);
                    client.setSoTimeout(3000);
                    client.getOutputStream().write("PING".getBytes(StandardCharsets.US_ASCII));
                    client.getOutputStream().flush();
                    String reply = new String(client.getInputStream().readNBytes(4), StandardCharsets.US_ASCII);
                    if (!reply.equals("PING")) throw new AssertionError("Bad echo");
                    System.out.println("Echo: " + reply);
                }
                served.get(5, TimeUnit.SECONDS);
            }
        }
    }
}

readNBytes(4) keeps reading toward four bytes or end-of-stream, so the code checks the resulting length or value. The client has a two-second connect timeout; the listener and accepted socket use three-second accept/read timeouts. SO_TIMEOUT bounds an individual blocking read, not an entire multi-read protocol exchange or a socket write. A slow peer making periodic progress can still stretch a larger operation. The Socket timeout documentation defines that distinction.

For a real length-prefixed protocol, validate the advertised size against a maximum before allocating a buffer. Define the byte order, character encoding, error messages and close behavior. A convenience readLine() call still needs a message-size policy when peers are untrusted.

Investigate the layer that failed

On a narrow screen, scroll the table horizontally. Keyboard users can focus the table and use the arrow keys.

Network symptoms and useful next evidence
SymptomWhat it establishesNext check
Name resolution failureThe requested name was not resolved on that pathHostname spelling, resolver configuration and environment-specific DNS.
Connection refusedThe attempted connection was actively rejectedHost, port, listening service and firewall behavior.
Connection timeoutEstablishment did not finish in the allowed timeRoute, firewall, remote availability and timeout policy.
TLS handshake failureSecure negotiation or validation failedCertificate chain, hostname, clock and protocol compatibility.
HTTP 401, 403, 404 or 500An HTTP responder returned that statusEndpoint, identity, authorization, routing and server logs.
Read timeout or short socket messageThe expected bytes did not arrive under the protocol policyPeer framing, flush/close behavior and the operation deadline.

Record request identifiers, timings and exception types, while redacting credentials and sensitive query strings. Check from the same host or container as the application: a successful browser request on a laptop can use a different resolver, proxy and trust store. Compare the controlled loopback success with the failing external step to narrow the investigation.

Continue with a related task