Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java sockets: connection deadlines, framing and stream ownership

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A TCP socket carries an ordered byte stream; it does not preserve application message boundaries.

Java 8+. This is a complete program using JDK classes.

Declare one record boundary

A line-based depot protocol ends each short record with a newline and uses UTF-8 on both sides. Without that framing rule, a receiver cannot decide whether a partial read is a complete record or the beginning of another transfer.

The program binds only to loopback, uses an operating-system selected port and sets connect, accept and read timeouts. It starts one local peer and waits for that peer’s future. No external service, credential or internet connection is used.

Read timeout and connect timeout cover different phases. Neither supplies an unlimited-input defense. This peer accepts only its tiny fixed fixture; a general server must bound record size while reading, validate every field and decide how to handle malformed or disconnected clients.

Close the owned endpoint

Flushing the writer makes the newline available to the peer. The socket is closed after one exchange, and the listening endpoint closes in the outer scope. The worker executor is also shut down. A successful flush means bytes reached the stream, not that a remote business action was committed.

Working program

Java
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
public class LocalDepotSocket {
    public static void main(String[] args) throws Exception {
        ExecutorService peer = Executors.newSingleThreadExecutor();
        try (ServerSocket server = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) {
            server.setSoTimeout(2000);
            Future<?> reply = peer.submit(() -> {
                try (Socket accepted = server.accept()) {
                    accepted.setSoTimeout(2000);
                    BufferedReader input = new BufferedReader(new InputStreamReader(accepted.getInputStream(), StandardCharsets.UTF_8));
                    PrintWriter output = new PrintWriter(new OutputStreamWriter(accepted.getOutputStream(), StandardCharsets.UTF_8), true);
                    if (!"PING".equals(input.readLine())) throw new IOException("Invalid record");
                    output.println("PONG");
                } catch (IOException failure) { throw new UncheckedIOException(failure); }
            });
            try (Socket client = new Socket()) {
                client.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), server.getLocalPort()), 2000);
                client.setSoTimeout(2000);
                PrintWriter output = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true);
                output.println("PING");
                BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
                System.out.println(input.readLine());
            }
            reply.get(2, TimeUnit.SECONDS);
        } finally {
            peer.shutdownNow();
            if (!peer.awaitTermination(2, TimeUnit.SECONDS)) throw new IllegalStateException("Peer did not stop");
        }
    }
}

Output

Output
PONG

Costs and boundaries

The exchange has a fixed payload. A real stream parser uses work proportional to received bytes and should cap both those bytes and its buffering. This sample has no TLS, authentication, retry deduplication or durable delivery; those requirements belong above the transport.

Common Mistakes

  • A TCP read can return part of a record.
  • Connect timeout is not read timeout.
  • Do not trust a peer to send a newline within an unbounded memory budget.

Read next

Byte ownership, Request deadlines.

java
socket-boundaries
Storage details