Java NIO (New I/O, since Java 1.4) provides non-blocking I/O operations via buffers and channels — an alternative to the stream-based java.io package.

NIO vs Classic I/O

Feature java.io (Streams) java.nio (Channels)
Data flow Byte-by-byte Buffer-based
Blocking Always blocking Blocking or non-blocking
Selectors Not available Multiplex channels
Best for Simple file I/O High-concurrency network I/O

Buffers

A buffer is a container for data of a primitive type:

  ByteBuffer buffer = ByteBuffer.allocate(1024); // heap buffer

buffer.put((byte) 'H');
buffer.put((byte) 'i');
buffer.flip(); // switch from write mode to read mode

while (buffer.hasRemaining()) {
    System.out.print((char) buffer.get());
}
  

Buffer Properties

Property Description
capacity Total size
position Current read/write index
limit End of readable/writable data
mark Saved position for reset

Key methods: put(), get(), flip(), clear(), rewind(), compact().

Direct Buffers

  ByteBuffer direct = ByteBuffer.allocateDirect(1024); // off-heap, faster for I/O
  

Direct buffers avoid copying between JVM heap and native memory — useful for network/file channels.

Channels

Channels transfer data to/from buffers:

  try (RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
     FileChannel channel = file.getChannel()) {

    ByteBuffer buffer = ByteBuffer.allocate(256);
    int bytesRead = channel.read(buffer);

    buffer.flip();
    while (buffer.hasRemaining()) {
        System.out.print((char) buffer.get());
    }
}
  

Common channel types:

Channel Purpose
FileChannel File read/write
SocketChannel TCP client
ServerSocketChannel TCP server
DatagramChannel UDP

Non-Blocking SocketChannel

  SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("example.com", 80));

while (!channel.finishConnect()) {
    // do other work while connecting
}

ByteBuffer buffer = ByteBuffer.allocate(256);
channel.read(buffer);
  

Selectors

A Selector multiplexes multiple channels — one thread handles many connections:

  Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select(); // blocks until events occur
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> iter = keys.iterator();

    while (iter.hasNext()) {
        SelectionKey key = iter.next();
        iter.remove();

        if (key.isAcceptable()) {
            // accept new connection
        } else if (key.isReadable()) {
            // read data
        }
    }
}
  

This is the foundation of high-performance network servers (Netty builds on similar concepts).

Scatter/Gather I/O

Read into or write from multiple buffers in one operation:

  ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = { header, body };

channel.read(buffers); // scatter read
channel.write(buffers); // gather write
  

Best Practices

  • Always call flip() before reading from a buffer after writing
  • Use try-with-resources for channels
  • Prefer NIO.2 (java.nio.file) for file operations — simpler API
  • Use NIO channels + selectors for high-concurrency network servers
  • Release direct buffers when no longer needed (GC handles this, but they are expensive to allocate)