Java ServerSocket
Accept incoming TCP connections in Java with ServerSocket and build a simple server.
The client end of a TCP connection is Socket. 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.
This chapter covers how to bind and accept connections, how to serve many clients at once, the role of the connection backlog, and the lifecycle discipline a long-running server needs.
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 one a client creates — so reading and writing its streams work exactly the same way.
By default accept() blocks forever. Call server.setSoTimeout(ms) first if you want it to throw SocketTimeoutException after a wait, which lets a server thread check a "should I keep running?" flag instead of hanging on a quiet port.
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. See the Executor framework and thread pools for how to size and manage the pool. With virtual threads (Java 21+), "thread per connection" stays cheap even at tens of thousands of clients, so you can often skip the pool and just submit each connection to its own virtual thread.
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 four-argument constructor adds the bind address: new ServerSocket(port, backlog, address). Passing InetAddress.getLoopbackAddress() makes the server reachable only from the same machine (127.0.0.1) — handy for local-only services and for the example below.
Releasing the port: SO_REUSEADDR
When a server stops, its listening port can linger in the OS in a TIME_WAIT state, and a fresh start may fail with "Address already in use". Setting SO_REUSEADDR lets a new ServerSocket bind to a port still in TIME_WAIT:
ServerSocket server = new ServerSocket(); // unbound
server.setReuseAddress(true);
server.bind(new InetSocketAddress(8080)); // now bind explicitlyThis is why production servers usually create an unbound ServerSocket, set options, then bind() — rather than passing the port to the constructor.
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.
When to use ServerSocket
ServerSocket is the right tool when you need a connection-oriented (TCP) server that you control at the byte/stream level: a custom protocol, a chat or game backend, a proxy, or a learning exercise. For request/response over HTTP you would normally use a higher-level server framework instead of writing the accept loop by hand. If you need connectionless (UDP) messaging — where there is no accept() and each packet stands alone — use datagram sockets. For background on addresses, ports, and the protocol stack, see the networking introduction.