W3docs

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. 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 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 explicitly

This 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.

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.

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.

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?
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?
Was this page helpful?