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. Use0to let the OS choose a free port (then read it back withgetLocalPort()).accept()blocks until a client connects, then returns aSocketrepresenting 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.
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 ordinarySocketfor that one client, while theServerSocketstayed open to accept the next — one listener, many connections. - Binding to port
0let the OS pick a free port, read back viagetLocalPort()and passed to the clients. The third constructor argument also pinned the server togetLoopbackAddress(), so it listened only on127.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, thenserver.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
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?