Differences Between Java NIO and IO, Channels, Buffers, and Their Comparison with Netty
This article explains the core differences between Java NIO and traditional IO, introduces the concepts of channels and buffers, describes buffer state management, provides a complete NIO selector code example, and compares the NIO workflow with Netty's model.
The core differences are that NIO processes data in blocks using channels and buffers, which makes it much more efficient than the byte‑stream based, blocking IO that relies on InputStream/OutputStream; NIO channels are bidirectional and support various buffer types such as read‑only, direct and indirect buffers.
A channel is an abstraction that replaces the traditional stream; all data must pass through a Channel object, and a Buffer acts as a container that holds data before it is written to or read from the channel.
A buffer is essentially an array (usually a byte array) that provides structured access to data; NIO defines several buffer subclasses like ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer, and ShortBuffer.
Buffer state is managed by four properties: capacity (total size), position (next element to read/write), limit (first element that should not be read/written), and mark (a saved position that can be restored with reset() ). Methods such as flip() , clear() and mark() transition the buffer between write and read modes.
Typical usage involves allocating a ByteBuffer, filling it with data, calling flip() to switch to read mode, processing the data, and finally calling clear() to reset the buffer for the next write cycle.
Example NIO selector code:
public void selector() throws IOException {
// allocate buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// open selector to poll channel states
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // non‑blocking
ssc.socket().bind(new InetSocketAddress(8080));
ssc.register(selector, SelectionKey.OP_ACCEPT); // register accept event
while (true) {
Set selectedKeys = selector.selectedKeys(); // get all keys
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
SocketChannel sc = ssChannel.accept(); // accept client
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
it.remove();
} else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
SocketChannel sc = (SocketChannel) key.channel();
while (true) {
buffer.clear();
int n = sc.read(buffer); // read data
if (n <= 0) {
break;
}
buffer.flip();
}
it.remove();
}
}
}
}The article then outlines the NIO workflow steps (creating ServerSocketChannel, binding, configuring non‑blocking mode, registering with a Selector, looping to handle OP_ACCEPT and OP_READ events) and contrasts them with Netty's workflow (creating EventLoopGroup, configuring ServerBootstrap, binding, and using ByteBuf instead of manual ByteBuffer handling). Key differences include Netty’s simplified accept handling, automatic buffer management, and built‑in decoders such as FixedLengthFrameDecoder that address TCP packet fragmentation.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.