Backend Development 16 min read

Understanding Java NIO: Channels, Buffers, Selectors and Example Code

This article explains Java NIO fundamentals—including synchronous vs asynchronous, blocking vs non‑blocking I/O—covers core components such as Channel, Buffer, and Selector, and provides complete example code for file operations and a single‑threaded server/client, comparing NIO with traditional I/O.

Java Captain
Java Captain
Java Captain
Understanding Java NIO: Channels, Buffers, Selectors and Example Code

Before diving into NIO, the article clarifies the often‑confused concepts of synchronous vs asynchronous and blocking vs non‑blocking I/O, noting that Java traditionally uses synchronous I/O which can be either blocking or non‑blocking, while true asynchronous I/O appears only in NIO.2 (JDK 1.7+).

Traditional I/O is blocking and wastes system resources because each connection requires a dedicated thread that remains idle while waiting for data, leading to high memory consumption and frequent context switches.

The core NIO component is the Channel , which represents a bidirectional connection to entities such as files or sockets and supports non‑blocking mode. Common implementations include FileChannel , DatagramChannel , SocketChannel , and ServerSocketChannel .

Data is transferred through a Buffer . Buffers have three important fields—capacity, position, and limit—and provide methods such as flip() , rewind() , clear() , and compact() to manage these pointers during read/write cycles.

An example demonstrates reading and writing a text file with FileChannel and two buffers ( ByteBuffer and CharBuffer ). The code shows how to append data, flip buffers for reading, decode UTF‑8 bytes, and handle partial multibyte characters using compact() .

The Selector component monitors multiple channels for events (ACCEPT, CONNECT, READ, WRITE). Channels must be set to non‑blocking mode before registration. The article explains how to open a selector, register a channel with an interest set (e.g., SelectionKey.OP_READ | SelectionKey.OP_WRITE ), and process ready keys.

A complete single‑threaded server example is provided. It creates a selector, registers a ServerSocketChannel for accept events, handles new connections, reads bytes until a terminating \0 byte, and echoes a response back to the client.

public class NioServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel listenChannel = ServerSocketChannel.open();
        listenChannel.bind(new InetSocketAddress(9999));
        listenChannel.configureBlocking(false);
        listenChannel.register(selector, SelectionKey.OP_ACCEPT);
        ByteBuffer buffer = ByteBuffer.allocate(100);
        while (true) {
            selector.select();
            Iterator
keyIter = selector.selectedKeys().iterator();
            while (keyIter.hasNext()) {
                SelectionKey key = keyIter.next();
                if (key.isAcceptable()) {
                    SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                    System.out.println("Connected: " + channel.getRemoteAddress());
                } else if (key.isReadable()) {
                    buffer.clear();
                    if (((SocketChannel) key.channel()).read(buffer) == -1) {
                        key.channel().close();
                        continue;
                    }
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        byte b = buffer.get();
                        if (b == 0) {
                            System.out.println();
                            buffer.clear();
                            buffer.put("Hello, Client!\0".getBytes());
                            buffer.flip();
                            while (buffer.hasRemaining()) {
                                ((SocketChannel) key.channel()).write(buffer);
                            }
                        } else {
                            System.out.print((char) b);
                        }
                    }
                }
                keyIter.remove();
            }
        }
    }
}

A minimal client program is also shown, which connects to the server, sends a message terminated by \0 , reads the response until the terminating byte, and prints it.

public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 9999);
        InputStream is = socket.getInputStream();
        OutputStream os = socket.getOutputStream();
        os.write("Hello, Server!\0".getBytes());
        int b;
        while ((b = is.read()) != 0) {
            System.out.print((char) b);
        }
        System.out.println();
        socket.close();
    }
}

The article concludes with a comparison: NIO excels when many connections transmit small amounts of data (single‑threaded, resource‑efficient), while traditional I/O may be preferable for few connections with large data volumes due to its synchronous, multi‑threaded nature.

Finally, it mentions that real‑world high‑performance servers often use Netty, an asynchronous framework built on Java NIO, and suggests further study of its advanced features.

BackendJavaNIOchannelNon-blocking I/OBufferSelector
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.