W3docs

Java ServerSocket

Accept incoming TCP connections in Java with ServerSocket and build a simple server.

Java ServerSocket

The previous chapter built the client end of a TCP connection. The server end is java.net.ServerSocket: it binds to a port, listens for incoming connections, and hands you a regular Socket for each client that arrives. One ServerSocket accepts many clients; each accept() call returns a separate connection.

Bind, accept, serve

ServerSocket server = new ServerSocket(8080);   // bind to port 8080
while (true) {
    Socket client = server.accept();            // blocks until a client connects
    handle(client);                             // read/write the client's streams
}
  • new ServerSocket(port) binds and starts listening. Use 0 to let the OS choose a free port (then read it back with getLocalPort()).
  • accept() blocks until a client connects, then returns a Socket representing that connection. The server socket itself keeps listening for the next one.

The connection accept() returns is an ordinary Socket — identical to the client's — so reading and writing work exactly as in the previous chapter.

Handling clients concurrently

accept() is blocking and each client may keep its connection open, so a single-threaded loop can serve only one client at a time. The classic fix is one thread (or pooled task) per connection:

ExecutorService pool = Executors.newFixedThreadPool(8);
while (running) {
    Socket client = server.accept();
    pool.submit(() -> handle(client));   // serve this client on a worker thread
}

The accept loop stays free to take the next connection while workers serve existing ones. (Java 21 virtual threads make "thread per connection" cheap even at thousands of clients.)

The backlog

new ServerSocket(port, backlog) sets the backlog — how many connections the OS may queue while your code is busy between accept() calls. Beyond it, new connections are refused. The default is typically 50.

A worked example: a concurrent loopback server

This program binds a ServerSocket to the loopback interface, accepts three clients on a background thread, and serves each on a thread pool — then fires three clients at it. Each worker greets its client and names the thread that served it, so the concurrency is visible.

java— editable, runs on the server

What to take from the run:

  • The server's whole job is bind → accept() → serve, repeated. accept() blocked until each client connected and then returned an ordinary Socket for that one client, while the ServerSocket stayed open to accept the next — one listener, many connections.
  • Binding to port 0 let the OS pick a free port, read back via getLocalPort() and passed to the clients. The third constructor argument also pinned the server to getLoopbackAddress(), so it listened only on 127.0.0.1 — the same address-and-port pair the clients dialed.
  • Each accepted connection was handed to a thread pool, so the accept loop never blocked on serving a slow client. The replies named different worker threads (pool-1-thread-1, -2, -3), making the per-connection concurrency concrete: three clients were served in parallel, not one after another.
  • handle() used try-with-resources on the client socket (try (client; …)), guaranteeing each connection is closed after its exchange. A server that forgets to close accepted sockets leaks descriptors fast, since it opens one per client.
  • Shutdown was explicit and ordered: stop accepting, pool.shutdown(), await termination, then server.close(). A long-running server must release its listening port deliberately, and outstanding worker tasks must be allowed to finish — the same discipline scales from this three-client demo to a real server.

Practice

Practice

A server uses a single-threaded loop: 'while (true) { Socket c = server.accept(); handle(c); }' where 'handle' keeps the connection open for the client's whole session. Under load, new clients connect but get no response until earlier ones disconnect. What is the cause and the standard fix?