Socket #

All network communication — HTTP, databases, chat, email — ultimately passes through sockets. A socket is the endpoint of a network connection: a pair of IP addresses and port numbers that allow two processes to exchange data, whether on the same machine or across the internet. Understanding how sockets work gives you the foundation for understanding higher-level protocols and building your own servers from scratch. Java provides a complete socket API in the java.net package — from ServerSocket and Socket for TCP, to DatagramSocket for UDP. This article covers how TCP communication works, building a server that can serve many clients simultaneously, sending text and binary data, setting timeouts, and designing a robust server.

Basic Concepts #

Before writing code, there are several networking concepts to understand:

ConceptExplanation
IP AddressThe unique address of each machine on a network. 127.0.0.1 (localhost) is the address of your own machine.
PortA number from 0–65535 that distinguishes applications on the same machine. Ports < 1024 usually require admin rights.
TCPA protocol that guarantees data arrives in order and intact. Requires a handshake before communication.
UDPA connectionless protocol that’s faster but doesn’t guarantee order or delivery.
SocketA communication endpoint — a combination of IP + port. One connection needs two sockets: one on the client, one on the server.
BacklogThe number of connections that can queue up while waiting for the server to process accept().
sequenceDiagram
    participant C as Client
    participant S as Server

    S->>S: ServerSocket.bind(port)
    S->>S: ServerSocket.accept() ← waiting
    C->>S: Socket.connect(ip, port)
    S->>S: accept() returns a new Socket
    Note over C,S: TCP connection established (3-way handshake)
    C->>S: send data (OutputStream)
    S->>C: reply with data (OutputStream)
    C->>S: close the connection

TCP — A Simple Server #

ServerSocket listens for incoming connections on a specific port. Each time a client connects, accept() returns a new Socket object representing the connection to that client.

Creating a Server #

import java.io.*;
import java.net.*;

public class SimpleServer {
    public static void main(String[] args) {
        int port = 8080;

        // try-with-resources: ServerSocket is closed automatically
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server listening on port " + port);

            // accept() blocks until a client connects
            try (Socket socket = serverSocket.accept()) {
                System.out.println("Client connected: " + socket.getInetAddress());

                // Wrap the streams with BufferedReader/PrintWriter for convenience
                BufferedReader input = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));
                PrintWriter output = new PrintWriter(
                    new OutputStreamWriter(socket.getOutputStream()), true); // autoFlush

                // Read a message from the client
                String message = input.readLine();
                System.out.println("Received: " + message);

                // Send a reply
                output.println("Echo: " + message);
            }

        } catch (IOException e) {
            System.err.println("Server error: " + e.getMessage());
        }
    }
}

Creating a Client #

import java.io.*;
import java.net.*;

public class SimpleClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 8080;

        try (Socket socket = new Socket(host, port);
             PrintWriter output = new PrintWriter(
                 new OutputStreamWriter(socket.getOutputStream()), true);
             BufferedReader input = new BufferedReader(
                 new InputStreamReader(socket.getInputStream()))) {

            System.out.println("Connected to " + host + ":" + port);

            // Send a message to the server
            output.println("Hello, Server!");

            // Read the reply
            String reply = input.readLine();
            System.out.println("Reply: " + reply);

        } catch (ConnectException e) {
            System.err.println("Connection failed — is the server running?");
        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

Running Both #

1. Run SimpleServer first
2. Run SimpleClient in another terminal

Server output:
  Server listening on port 8080
  Client connected: /127.0.0.1
  Received: Hello, Server!

Client output:
  Connected to localhost:8080
  Reply: Echo: Hello, Server!

A Multi-Client Server #

The simple server above can only serve one client — after the first connection closes, the program ends. Real applications need to serve many clients simultaneously. The solution: handle each connection on a separate thread using ExecutorService.

Server with a Thread Pool #

import java.io.*;
import java.net.*;
import java.util.concurrent.*;

public class MultiClientServer {
    private static final int PORT    = 8080;
    private static final int THREADS = 10; // max 10 simultaneous clients

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(THREADS);

        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            serverSocket.setReuseAddress(true); // allow port reuse after a quick restart
            System.out.println("Server ready on port " + PORT);

            while (true) { // keep accepting new connections
                Socket socket = serverSocket.accept();
                System.out.println("New client: " + socket.getInetAddress() + ":" + socket.getPort());

                // Delegate the handling to the thread pool
                pool.submit(new HandleClient(socket));
            }

        } catch (IOException e) {
            System.err.println("Server stopped: " + e.getMessage());
        } finally {
            pool.shutdown();
        }
    }
}

// Handler for one client — runs on a separate thread
class HandleClient implements Runnable {
    private final Socket socket;

    public HandleClient(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        String address = socket.getInetAddress().getHostAddress();

        try (socket; // Java 9+: can use try-with-resources for an existing variable
             BufferedReader input = new BufferedReader(
                 new InputStreamReader(socket.getInputStream()));
             PrintWriter output = new PrintWriter(
                 new OutputStreamWriter(socket.getOutputStream()), true)) {

            System.out.println("[" + address + "] Connected");

            String line;
            while ((line = input.readLine()) != null) {
                System.out.println("[" + address + "] → " + line);

                if ("QUIT".equalsIgnoreCase(line)) {
                    output.println("Goodbye!");
                    break;
                }

                // Echo back with an added timestamp
                output.println("[" + java.time.LocalTime.now() + "] " + line);
            }

        } catch (IOException e) {
            System.err.println("[" + address + "] Connection dropped: " + e.getMessage());
        } finally {
            System.out.println("[" + address + "] Disconnected");
        }
    }
}
flowchart TD
    A["ServerSocket\n(port 8080)"] -->|"accept()"| B["Client 1 connection"]
    A -->|"accept()"| C["Client 2 connection"]
    A -->|"accept()"| D["Client N connection"]
    B -->|"submit()"| E["Thread Pool\n(ExecutorService)"]
    C -->|"submit()"| E
    D -->|"submit()"| E
    E --> F["Worker Thread 1\n(HandleClient)"]
    E --> G["Worker Thread 2\n(HandleClient)"]
    E --> H["Worker Thread N\n(HandleClient)"]

Two-Way Communication — A Simple Chat #

A chat scenario requires both sides (client and server) to send and receive messages independently. This needs two threads on the client side: one for reading keyboard input, one for receiving messages from the server.

Chat Server #

import java.io.*;
import java.net.*;
import java.util.concurrent.*;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class ChatServer {
    private static final int PORT = 9090;
    // A set of all active PrintWriters — for broadcasting to all clients
    private static final Set<PrintWriter> allClients = new CopyOnWriteArraySet<>();

    public static void main(String[] args) throws IOException {
        ExecutorService pool = Executors.newCachedThreadPool();

        try (ServerSocket server = new ServerSocket(PORT)) {
            System.out.println("Chat server on port " + PORT);

            while (true) {
                Socket socket = server.accept();
                pool.submit(() -> handleChatClient(socket));
            }
        }
    }

    private static void handleChatClient(Socket socket) {
        PrintWriter output = null;
        try {
            BufferedReader input = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            output = new PrintWriter(
                new OutputStreamWriter(socket.getOutputStream()), true);

            allClients.add(output);

            // Ask for the username
            output.println("Enter your name:");
            String name = input.readLine();
            broadcast("[" + name + " joined]", null);

            String message;
            while ((message = input.readLine()) != null) {
                if ("QUIT".equalsIgnoreCase(message)) break;
                broadcast(name + ": " + message, null);
            }

            broadcast("[" + name + " left]", null);

        } catch (IOException e) {
            System.err.println("Client disconnected: " + e.getMessage());
        } finally {
            if (output != null) allClients.remove(output);
            try { socket.close(); } catch (IOException ignored) {}
        }
    }

    // Send a message to all connected clients
    private static void broadcast(String message, PrintWriter except) {
        System.out.println("Broadcast: " + message);
        for (PrintWriter pw : allClients) {
            if (pw != except) pw.println(message);
        }
    }
}

Chat Client #

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class ChatClient {
    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("localhost", 9090);
             BufferedReader input = new BufferedReader(
                 new InputStreamReader(socket.getInputStream()));
             PrintWriter output = new PrintWriter(
                 new OutputStreamWriter(socket.getOutputStream()), true)) {

            // Thread for reading messages from the server (runs in the background)
            Thread reader = new Thread(() -> {
                try {
                    String line;
                    while ((line = input.readLine()) != null) {
                        System.out.println(line);
                    }
                } catch (IOException e) {
                    System.out.println("Connection to the server was lost.");
                }
            });
            reader.setDaemon(true); // dies when the main thread finishes
            reader.start();

            // Main thread: read keyboard input and send it to the server
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()) {
                String inputLine = scanner.nextLine();
                output.println(inputLine);
                if ("QUIT".equalsIgnoreCase(inputLine)) break;
            }
        }
    }
}

Timeouts and Socket Options #

Without timeouts, socket I/O operations can block forever — for example when the server doesn’t respond. Always set a reasonable timeout for applications dealing with external networks.

Setting Timeouts #

// Timeout while waiting for accept() — how long the server waits for new connections
ServerSocket serverSocket = new ServerSocket(8080);
serverSocket.setSoTimeout(30_000); // 30 seconds, throws SocketTimeoutException when expired

// Timeout for read() operations on a client socket
Socket socket = new Socket();
socket.setSoTimeout(5_000); // 5 seconds per read operation

// Timeout for the connection process itself
socket.connect(new InetSocketAddress("example.com", 80), 10_000); // 10 seconds

// Example with timeout handling
try (ServerSocket server = new ServerSocket(8080)) {
    server.setSoTimeout(60_000); // wait for a client at most 60 seconds

    try {
        Socket client = server.accept();
        // process the client...
    } catch (SocketTimeoutException e) {
        System.out.println("No client connected within 60 seconds.");
    }
}

Important Socket Options #

Socket socket = new Socket("localhost", 8080);

// TCP_NODELAY: disable Nagle's algorithm — send data immediately without buffering
// Important for real-time applications needing low latency (games, chat)
socket.setTcpNoDelay(true);

// SO_KEEPALIVE: periodically send keepalive packets to detect dead connections
socket.setKeepAlive(true);

// SO_LINGER: wait until data is sent when the socket closes (in seconds)
socket.setSoLinger(true, 5); // wait at most 5 seconds

// SO_RCVBUF / SO_SNDBUF: receive and send buffer sizes
socket.setReceiveBufferSize(65536); // 64 KB
socket.setSendBufferSize(65536);

// Useful socket information
System.out.println("Local IP:  " + socket.getLocalAddress());
System.out.println("Local port: " + socket.getLocalPort());
System.out.println("Remote IP: " + socket.getInetAddress());
System.out.println("Remote port: " + socket.getPort());
System.out.println("Connected: " + socket.isConnected());
System.out.println("Closed:   " + socket.isClosed());

Binary File Transfer #

Besides text, sockets can be used to transfer binary files — images, documents, or any data — by directly reading and writing bytes from the streams.

File Receiving Server #

import java.io.*;
import java.net.*;
import java.nio.file.*;

public class ReceiveFileServer {
    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(7070);
             Socket socket = server.accept()) {

            System.out.println("Client connected, receiving file...");

            DataInputStream dis = new DataInputStream(
                new BufferedInputStream(socket.getInputStream()));

            // Receive the file name and size first
            String fileName = dis.readUTF();
            long size     = dis.readLong();
            System.out.printf("Receiving '%s' (%d bytes)%n", fileName, size);

            // Receive the file contents
            Path output = Path.of("received-" + fileName);
            try (OutputStream fos = Files.newOutputStream(output)) {
                byte[] buffer = new byte[8192];
                long remaining = size;
                int read;

                while (remaining > 0 &&
                       (read = dis.read(buffer, 0, (int) Math.min(buffer.length, remaining))) != -1) {
                    fos.write(buffer, 0, read);
                    remaining -= read;
                }
            }

            System.out.println("File received successfully: " + output.toAbsolutePath());
        }
    }
}

File Sending Client #

import java.io.*;
import java.net.*;
import java.nio.file.*;

public class SendFileClient {
    public static void main(String[] args) throws IOException {
        Path file = Path.of("document.pdf"); // the file to send

        try (Socket socket = new Socket("localhost", 7070);
             DataOutputStream dos = new DataOutputStream(
                 new BufferedOutputStream(socket.getOutputStream()))) {

            // Send the file name and size first
            dos.writeUTF(file.getFileName().toString());
            dos.writeLong(Files.size(file));
            dos.flush();

            // Send the file contents
            Files.copy(file, dos);
            dos.flush();

            System.out.println("File sent successfully: " + file);
        }
    }
}

UDP — Connectionless Communication #

UDP (User Datagram Protocol) doesn’t establish a connection like TCP. Each packet (datagram) is sent independently and may arrive out of order or not at all. This makes it faster but less reliable — suitable for video streaming, online games, or DNS.

UDP Server #

import java.net.*;

public class UDPServer {
    public static void main(String[] args) throws Exception {
        try (DatagramSocket socket = new DatagramSocket(9999)) {
            System.out.println("UDP server listening on port 9999");
            byte[] buffer = new byte[1024];

            while (true) {
                // Receive a datagram
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet); // blocks until data arrives

                String message = new String(packet.getData(), 0, packet.getLength());
                System.out.println("Received from " + packet.getAddress() + ": " + message);

                // Send a reply to the same address and port
                String reply = "UDP Echo: " + message;
                byte[] replyData = reply.getBytes();
                DatagramPacket replyPacket = new DatagramPacket(
                    replyData, replyData.length,
                    packet.getAddress(), packet.getPort()
                );
                socket.send(replyPacket);
            }
        }
    }
}

UDP Client #

import java.net.*;

public class UDPClient {
    public static void main(String[] args) throws Exception {
        try (DatagramSocket socket = new DatagramSocket()) {
            socket.setSoTimeout(3000); // 3-second timeout for receive

            InetAddress serverAddress = InetAddress.getByName("localhost");
            int serverPort = 9999;

            // Send a datagram
            String message = "Hello UDP!";
            byte[] data = message.getBytes();
            DatagramPacket sendPacket = new DatagramPacket(
                data, data.length, serverAddress, serverPort);
            socket.send(sendPacket);

            // Receive the reply
            byte[] buffer = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
            socket.receive(receivePacket);

            System.out.println("Reply: " + new String(receivePacket.getData(), 0, receivePacket.getLength()));
        }
    }
}

Robust Error Handling #

Network applications must handle various error conditions — sudden disconnections, unresponsive servers, ports already in use.

Automatic Reconnect on the Client Side #

public class ResilientClient {
    private static final int MAX_ATTEMPTS = 5;
    private static final int DELAY_MS   = 2000;

    public static Socket connectWithRetry(String host, int port)
            throws IOException, InterruptedException {

        for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            try {
                Socket socket = new Socket();
                socket.connect(new InetSocketAddress(host, port), 5000);
                System.out.println("Connected after " + attempt + " attempts");
                return socket;
            } catch (ConnectException e) {
                System.err.printf("Attempt %d/%d failed. Retrying in %d ms...%n",
                    attempt, MAX_ATTEMPTS, DELAY_MS);
                if (attempt < MAX_ATTEMPTS) {
                    Thread.sleep(DELAY_MS);
                }
            }
        }
        throw new IOException("Failed to connect after " + MAX_ATTEMPTS + " attempts");
    }

    public static void main(String[] args) {
        try {
            Socket socket = connectWithRetry("localhost", 8080);
            // use the socket...
            socket.close();
        } catch (IOException | InterruptedException e) {
            System.err.println("Connection failed entirely: " + e.getMessage());
        }
    }
}

Handling Exceptions Correctly #

// ANTI-PATTERN: catching Exception too broadly, hiding error details
try {
    Socket socket = new Socket("host", 8080);
    // ...
} catch (Exception e) {
    e.printStackTrace(); // ✗ uninformative, no recovery
}

// CORRECT: catch specific exceptions and handle them appropriately
try {
    Socket socket = new Socket();
    socket.setSoTimeout(5000);
    socket.connect(new InetSocketAddress("host", 8080), 10_000);

    // use the socket...

} catch (ConnectException e) {
    System.err.println("Server unreachable: " + e.getMessage());
    // → try another server, or inform the user
} catch (SocketTimeoutException e) {
    System.err.println("Timeout: " + e.getMessage());
    // → retry or abort
} catch (UnknownHostException e) {
    System.err.println("Host not found: " + e.getMessage());
    // → check the host name
} catch (IOException e) {
    System.err.println("I/O error: " + e.getMessage());
    // → log and handle
}

TCP vs UDP — When to Use Each #

Use TCP when:
  ✓ Data must arrive complete and in order (file transfer, HTTP, databases)
  ✓ Data loss is unacceptable
  ✓ A few extra milliseconds of latency aren't critical

Use UDP when:
  ✓ Speed matters more than reliability (real-time games, video streaming)
  ✓ Occasional loss is fine — data is already stale before it could be retried
  ✓ You need broadcast or multicast to many receivers at once
  ✓ Building a custom protocol on top of UDP (QUIC, WebRTC)

TCP server best practices:
  ✓ Always use a thread pool (ExecutorService) for multi-client servers
  ✓ Set SO_TIMEOUT so no thread blocks forever
  ✓ Use setReuseAddress(true) so the port can be reused on restart
  ✓ Close sockets in a finally block or use try-with-resources
  ✓ Log client addresses for debugging and monitoring

Summary #

  • ServerSocket waits for connections, Socket makes connectionsaccept() blocks until a client connects and returns a new Socket for communication.
  • One client = one thread — for servers serving many clients, delegate each connection to a thread pool with ExecutorService. Don’t create a new thread manually for every connection.
  • Always set timeoutssetSoTimeout() prevents read() operations from blocking forever. Without a timeout, one slow client can block the entire thread pool.
  • Use BufferedReader/PrintWriter for text — wrap socket.getInputStream() and socket.getOutputStream() with buffers for efficiency and ease of use.
  • Use DataInputStream/DataOutputStream for structured data — they provide writeUTF(), readLong(), writeInt(), etc., ensuring consistent byte formats across platforms.
  • Close sockets with try-with-resources — a socket is an OS resource that must be closed. Forgetting to close sockets causes file descriptor leaks.
  • TCP for reliability, UDP for speed — TCP guarantees order and delivery but has overhead. UDP is faster but guarantees nothing.
  • setReuseAddress(true) for fast restarts — without it, the server may fail to bind to the same port immediately after a restart because the OS is still “holding” the port.

← Previous: I/O   Next: Web Socket →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact