Skip to content

NIO Channels and Buffers — FileChannel, ByteBuffer, SocketChannel, and Selectors

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about NIO Channels and Buffers. We cover key concepts, practical examples, and best practices to help you master this topic.

Java NIO channels and buffers provide high-performance I/O operations with non-blocking capabilities and direct memory access. Introduced in Java 1.4, NIO (New I/O) addresses limitations of the standard I/O stream model by adding buffer-oriented I/O, channel-based operations, and non-blocking multiplexed I/O through selectors.

What You'll Learn

  • Channels: FileChannel, SocketChannel, ServerSocketChannel
  • ByteBuffer: creation, reading, writing, flipping, clearing
  • Non-blocking I/O with SocketChannel
  • Selectors for multiplexed I/O

Why It Matters

Standard I/O streams block the calling thread. For high-performance servers handling thousands of connections, blocking I/O requires one thread per connection — which does not scale. NIO's non-blocking channels let one thread manage many connections.

Real-World Use

Web servers (Netty, Undertow), database drivers, and high-throughput network services use NIO channels. FileChannel provides memory-mapped files for extremely fast file I/O.


FileChannel

Reading a file with FileChannel:

try (FileChannel channel = FileChannel.open(
        Path.of("data.bin"), StandardOpenOption.READ)) {

    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int bytesRead = channel.read(buffer); // reads into buffer
    // ... process buffer
}

Writing:

try (FileChannel channel = FileChannel.open(
        Path.of("output.bin"),
        StandardOpenOption.CREATE,
        StandardOpenOption.WRITE)) {

    ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes(StandardCharsets.UTF_8));
    channel.write(buffer);
}

FileChannel Position

long position = channel.position();
channel.position(100); // seek to position 100

FileChannel Size and Truncate

long fileSize = channel.size();
channel.truncate(1024); // truncate to 1024 bytes

Memory-Mapped Files

For extremely fast file access, map a file region into memory:

try (FileChannel channel = FileChannel.open(
        Path.of("large.dat"), StandardOpenOption.READ)) {

    MappedByteBuffer buffer = channel.map(
        FileChannel.MapMode.READ_ONLY, 0, channel.size());

    // Access data as if it were a byte array
    byte first = buffer.get(0);
    buffer.position(100);
    // ...
}

Memory-mapped files can be faster than read/write because the OS handles page faults transparently.

ByteBuffer

A buffer is a fixed-size container with four key properties:

  • capacity: maximum elements
  • position: current read/write position
  • limit: end of readable/writable area
  • mark: remembered position (optional)
ByteBuffer buffer = ByteBuffer.allocate(256);
// OR from array
byte[] data = {1, 2, 3};
ByteBuffer buffer = ByteBuffer.wrap(data);

Writing and Reading

ByteBuffer buffer = ByteBuffer.allocate(8);

// Write
buffer.put((byte) 0x01);
buffer.putInt(42);
buffer.putDouble(3.14);

// Flip: switch from write mode to read mode
buffer.flip();

// Read
byte b = buffer.get();
int i = buffer.getInt();
double d = buffer.getDouble();

// Clear: reset for writing again
buffer.clear();

Compact

Moves unread data to the beginning, sets position after unread data:

buffer.compact();
// Useful when you read some data and want to write more

Direct Buffers

ByteBuffer directBuffer = ByteBuffer.allocateDirect(4096);
// Memory outside the JVM heap — faster for I/O

Direct buffers avoid copying data between JVM heap and native memory. Allocation is more expensive, but I/O operations are faster.

SocketChannel

Non-blocking network client:

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

// Wait for connection
while (!channel.finishConnect()) {
    // do other work
}

// Read
ByteBuffer buffer = ByteBuffer.allocate(4096);
int bytesRead = channel.read(buffer);

// Write
buffer.flip();
channel.write(buffer);

ServerSocketChannel

ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);

while (true) {
    SocketChannel client = server.accept(); // non-blocking
    if (client != null) {
        // handle client
    }
}

Selectors

A Selector multiplexes multiple channels into a single thread:

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 at least one channel is ready
    Set<SelectionKey> keys = selector.selectedKeys();

    for (SelectionKey key : keys) {
        if (key.isAcceptable()) {
            // accept new connection
        } else if (key.isReadable()) {
            // read from channel
        } else if (key.isWritable()) {
            // write to channel
        }
        keys.remove(key);
    }
}

SelectionKey Operations

SelectionKey key = channel.register(selector, interestOps);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
key.attach(Object attachment); // attach user data
Object data = key.attachment(); // retrieve attached data
key.cancel(); // unregister

Common Mistakes

  1. Forgetting to call flip() before reading. After writing to a buffer, position is at the end. flip() sets limit to position and position to 0, enabling reading.
  2. Forgetting clear() before reusing a buffer. Without clear, position and limit are at incorrect states for writing.
  3. Using blocking mode with Selector. Channels registered with a Selector must be in non-blocking mode.
  4. Not iterating over selectedKeys correctly. Always call keys.remove(key) after processing to avoid processing the same key again.
  5. Allocating direct buffers unnecessarily. Direct buffers are for long-lived, I/O-intensive operations. For small or infrequent I/O, use heap buffers.

Practice Questions

1. What is the difference between flip() and clear() on a ByteBuffer?
flip() switches from write mode to read mode (limit = position, position = 0). clear() resets for writing (position = 0, limit = capacity).

2. What is a direct buffer?
A buffer allocated outside the JVM heap. It is faster for I/O operations because the JVM avoids copying data between heap and native memory during read/write operations.

3. What does a Selector do?
A Selector monitors multiple channels for readiness events (connect, accept, read, write). It allows a single thread to manage multiple I/O channels efficiently.

4. Why must channels registered with a Selector be non-blocking?
Blocking channels would defeat the purpose of multiplexing — a blocking read on one channel would block all channels managed by the selector.

5. What is a memory-mapped file?
A file region mapped directly into virtual memory. The OS handles loading pages on demand. Reading/writing the buffer reads/writes the file directly, potentially much faster than explicit read/write calls.

Challenge Question:
Implement a simple echo server using NIO selectors. The server should accept connections, read data from clients, and echo it back. Handle multiple concurrent clients with a single thread. Use non-blocking channels and a selector.

FAQ

What is the difference between NIO and NIO.2?

NIO (Java 1.4) introduced channels, buffers, and selectors. NIO.2 (Java 7) added the Path and Files APIs, asynchronous channels, and the watch service. Both are in the java.nio package.

When should I use NIO over standard I/O?

Use NIO for high-performance, non-blocking, or multiplexed I/O (web servers, proxies). Use standard I/O for simple file operations or when code readability is more important than raw throughput.

What is `ByteBuffer`'s default byte order?

Big-endian. Use buffer.order(ByteOrder.LITTLE_ENDIAN) for little-endian data (e.g., x86 systems).

Can I use FileChannel for concurrent access?

FileChannel is not thread-safe. Use synchronization or separate channel instances per thread. Note that file positions are shared across threads in the same channel.

What is an AsynchronousFileChannel?

A channel that supports asynchronous I/O operations. Instead of blocking or using selectors, AsynchronousFileChannel.read() returns a Future<Integer> or accepts a CompletionHandler. Available in Java 7+.

Mini Project

Write a program NioDemo.java that:

  1. Creates a file with 1 million integers using FileChannel and ByteBuffer (write in batches of 4096 bytes)
  2. Reads them back using the same technique, measuring read time
  3. Creates a memory-mapped version and compares read speed
  4. Implements a simple non-blocking chat server using ServerSocketChannel and Selector
  5. The server accepts connections and broadcasts messages to all connected clients
  6. A simple client connects, sends a message, and receives the broadcast
  7. Demonstrates direct vs heap buffer allocation with timing comparison

What's Next

NIO channels handle raw bytes efficiently. But when you need to persist entire object graphs, Serialization is the standard approach. Lesson 43 covers serialization — Serializable interface, transient fields, ObjectOutputStream, and versioning with serialVersionUID.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro