Socket Programming in Java
Socket programming means writing programs that talk to each other over a TCP connection. Java gives you two classes for it. A ServerSocket waits on a port for clients to connect. A Socket is one end of an established connection, with an input stream to read what the other side sends and an output stream to write to it. The client creates a Socket to connect; the server gets a Socket for each client from accept(). In this lesson, you will learn the steps on each side, the methods and exceptions involved, and build an echo server that serves many clients at the same time.
The Steps
| Step | Server | Client |
|---|---|---|
| 1 | new ServerSocket(port) | |
| 2 | accept() waits for a client | new Socket(host, port) connects |
| 3 | accept() returns a Socket | |
| 4 | Read and write through the streams | Read and write through the streams |
| 5 | Close the Socket; go back to step 2 | Close the Socket |
Methods
| Method | Description |
|---|---|
new ServerSocket(port) | It is used to listen on a port; port 0 picks any free one. |
serverSocket.accept() | It waits for a client and returns a Socket connected to it. |
serverSocket.getLocalPort() | It returns the port actually in use. |
new Socket(host, port) | It connects to a server. |
socket.connect(address, timeoutMillis) | It connects an unconnected Socket with a time limit. |
socket.getInputStream() | It returns the stream of data arriving from the other side. |
socket.getOutputStream() | It returns the stream for sending data to the other side. |
socket.setSoTimeout(millis) | It sets how long a read may wait before SocketTimeoutException. |
socket.getRemoteSocketAddress() | It returns the other side's address and port. |
close() | It closes the socket and its streams. |
Checkpoint: An Echo Server with Many Clients
An echo server sends back what it receives. This one replies in upper case, says goodbye when a client sends bye, and serves three clients at the same time:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class EchoServer {
// Server side: one virtual thread per connected client.
static void serve(ServerSocket server) {
try (ExecutorService pool =
Executors.newVirtualThreadPerTaskExecutor()) {
while (true) {
Socket client = server.accept();
pool.submit(() -> handle(client));
}
} catch (IOException e) {
// accept() fails when the server socket is closed: time to stop.
}
}
static void handle(Socket client) {
try (client;
BufferedReader in = new BufferedReader(
new InputStreamReader(client.getInputStream()));
PrintWriter out =
new PrintWriter(client.getOutputStream(), true)) {
String line;
while ((line = in.readLine()) != null) {
if (line.equals("bye")) {
out.println("Goodbye!");
break;
}
out.println("ECHO: " + line.toUpperCase());
}
} catch (IOException e) {
System.out.println("Client problem: " + e.getMessage());
}
}
// Client side: send two lines, read two replies, then say bye.
static String talk(int port, String name) throws IOException {
try (Socket socket = new Socket("localhost", port);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out =
new PrintWriter(socket.getOutputStream(), true)) {
socket.setSoTimeout(3000);
out.println("hello from " + name);
String first = in.readLine();
out.println("bye");
String last = in.readLine();
return name + " got [" + first + "] then [" + last + "]";
}
}
public static void main(String[] args) throws Exception {
try (ServerSocket server = new ServerSocket(0)) {
int port = server.getLocalPort();
Thread.ofVirtual().start(() -> serve(server));
try (ExecutorService clients =
Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> results = List.of(
clients.submit(() -> talk(port, "Navika")),
clients.submit(() -> talk(port, "Srihitha")),
clients.submit(() -> talk(port, "Advitha")));
for (Future<String> r : results) {
System.out.println(r.get());
}
}
}
System.out.println("Server stopped");
}
}Navika got [ECHO: HELLO FROM NAVIKA] then [Goodbye!]
Srihitha got [ECHO: HELLO FROM SRIHITHA] then [Goodbye!]
Advitha got [ECHO: HELLO FROM ADVITHA] then [Goodbye!]
Server stoppedExplanation:
serveloops forever onaccept(). Each time a client connects, it hands the newSockettohandleon a new virtual thread and immediately goes back to waiting. That is why one slow client can't hold up the others.handlewraps the socket's streams in aBufferedReaderand aPrintWriterto work with lines of text. Thetrueinnew PrintWriter(..., true)turns on auto-flush, so eachprintlnis sent right away.readLine()returnsnullwhen the client closes the connection, which ends the loop.try (client; ...)closes the socket whatever happens.talkis a client: connect, set a read timeout, send a line, read the reply, sendbye, read the goodbye.- Stopping: when
mainleaves its try block, theServerSocketis closed. The blockedaccept()then throws anIOException, whichservetreats as the signal to stop.
Before virtual threads, a server like this needed a thread pool, and the pool's size limited how many clients it could serve. With one virtual thread per client, the same simple blocking code can handle thousands of connections (see "Virtual Threads in Java").
What Can Go Wrong
Let's see an example of the three failures you will meet most often:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Fail {
public static void main(String[] args) throws Exception {
// 1. Nothing is listening on this port.
int freePort;
try (ServerSocket probe = new ServerSocket(0)) {
freePort = probe.getLocalPort();
}
try (Socket s = new Socket()) {
s.connect(new InetSocketAddress("localhost", freePort), 2000);
} catch (IOException e) {
System.out.println("1. " + e.getClass().getSimpleName());
}
// 2. The server accepts the connection but never sends anything.
try (ServerSocket silent = new ServerSocket(0);
Socket s = new Socket("localhost", silent.getLocalPort())) {
s.setSoTimeout(300);
s.getInputStream().read();
} catch (IOException e) {
System.out.println("2. " + e.getClass().getSimpleName());
}
// 3. Two servers try to use the same port.
try (ServerSocket first = new ServerSocket(0);
ServerSocket second = new ServerSocket(first.getLocalPort())) {
System.out.println("should not happen");
} catch (IOException e) {
System.out.println("3. " + e.getClass().getSimpleName());
}
}
}1. ConnectException
2. SocketTimeoutException
3. BindException| Exception | Meaning | Usual fix |
|---|---|---|
ConnectException | Nothing is listening at that address and port ("connection refused") | Check the host, the port, and that the server is running |
SocketTimeoutException | A connect or a read took longer than the timeout | Retry, or report that the server is slow |
BindException | The port is already used by another program | Choose another port, or stop the other program |
UnknownHostException | The host name can't be resolved | Check the name and the DNS |
SocketException: Connection reset | The other side closed the connection abruptly | Reconnect; make the protocol tolerate it |
All of them are subclasses of IOException.
Text and Binary Protocols
The echo server speaks a line-based text protocol: each message ends with a newline, so both sides know where a message stops. TCP itself has no message boundaries; it is one long stream of bytes. Every protocol must define its own, in one of two usual ways:
- A delimiter: such as a newline. Simple and readable; HTTP headers work this way.
- A length prefix: send the size first, then exactly that many bytes.
DataOutputStream.writeIntandDataInputStream.readFullymake this easy for binary data.
Common Mistakes
Forgetting to flush. With a buffered writer and no auto-flush, the message stays in the buffer and both sides wait for each other forever.
Handling clients one at a time. While the server talks to one client, the others wait. Use a thread per client.
Assuming one read returns one whole message. A single read may return part of a message, or parts of two. Use a delimiter or a length prefix.
Not closing sockets. Each open socket uses an operating system resource; leaks eventually stop the server from accepting anyone.
Interview Corner
How does a client-server program work with sockets in Java?
The server creates a ServerSocket bound to a port and calls accept(), which blocks until a client connects and then returns a Socket for that client. The client creates a Socket with the server's host and port. Each side reads from getInputStream() and writes to getOutputStream(), and closes the socket when finished. To serve several clients at once, the server handles each accepted socket on its own thread, traditionally from a thread pool and now often on a virtual thread per connection.
What is the difference between Socket and ServerSocket?
ServerSocket only listens for incoming connection requests on a port and creates sockets; it carries no data itself. Socket is an endpoint of an established connection and provides the input and output streams. A client uses only Socket; a server uses one ServerSocket plus one Socket per connected client.
Practice
- Easy: Add a command to the echo server: when a client sends
time, reply with the current time. - Harder: Turn the echo server into a chat room: keep the connected clients' writers in a thread-safe collection and send every line to all of them.
Key Takeaways
Was this tutorial helpful?