Java NIO & Netty Cheat Sheet
A practical reference covering Java’s non-blocking I/O model, Netty’s pipeline architecture, and the zero-copy techniques that make high-throughput network servers fast.
NIO Fundamentals
Java NIO (New I/O, Java 1.4) provides non-blocking I/O via Channels, Buffers, and Selectors. One thread can multiplex many connections using a Selector — the OS notifies when a channel is ready for I/O.
Traditional I/O (blocking) NIO (non-blocking, multiplexed)
────────────────────────── ─────────────────────────────
Thread 1 ──▶ Socket 1 (blocks)
Thread 2 ──▶ Socket 2 (blocks) Selector ──▶ [Socket1, Socket2, Socket3...]
Thread 3 ──▶ Socket 3 (blocks) Single thread handles ALL ready channels
... OS notifies which channels are ready
// NIO Selector — single thread, many connections
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false); // non-blocking mode
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()) {
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel ch = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
ch.read(buf);
buf.flip(); // switch from write mode to read mode
// process buf...
}
}
keys.clear();
}
Netty’s Pipeline Model
Netty wraps NIO with a clean pipeline model. EventLoopGroup manages a pool of threads, each running a Selector loop. ChannelPipeline chains handlers — each handler processes inbound or outbound events.
Netty Server Architecture
┌─────────────────────────────────────────────────┐
│ ServerBootstrap │
│ BossGroup (1 thread) WorkerGroup (N threads) │
│ ┌───────────────┐ ┌───────────────────┐ │
│ │ NioEventLoop │ │ NioEventLoop x N │ │
│ │ (accepts) │─────▶│ (read/write/exec)│ │
│ └───────────────┘ └────────┬──────────┘ │
└───────────────────────────────────┼─────────────┘
│
Channel Pipeline
┌───────────────────────┐
inbound │ ByteToMessageDecoder │ decode bytes → POJO
▼ ├───────────────────────┤
│ BusinessLogicHandler │ process
▼ ├───────────────────────┤
outbound │ MessageToByteEncoder │ encode POJO → bytes
└───────────────────────┘
// Netty server setup
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup(); // defaults to 2 * CPU cores
try {
ServerBootstrap b = new ServerBootstrap();
b.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(
new LengthFieldBasedFrameDecoder(8192, 0, 4),
new MessageDecoder(),
new BusinessHandler(),
new MessageEncoder()
);
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
// Handler example
public class BusinessHandler extends SimpleChannelInboundHandler<Request> {
protected void channelRead0(ChannelHandlerContext ctx, Request req) {
Response resp = process(req);
ctx.writeAndFlush(resp); // non-blocking write
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
Zero-Copy & Pooled Buffers
Netty uses off-heap pooled buffers (PooledByteBuf) and zero-copy techniques to minimize data movement. For file transfer, sendfile() allows the kernel to move data directly from disk to socket without a userspace copy.
Normal copy path: Zero-copy path (sendfile):
Disk → Kernel buffer Disk → Kernel buffer
→ User buffer (copy 1) → Socket buffer (kernel copy only)
→ Socket buffer (copy 2)
→ NIC (copy 3) → NIC
Userspace never touched the data
// Zero-copy file transfer with Netty
public void channelRead0(ChannelHandlerContext ctx, Request req) {
RandomAccessFile file = new RandomAccessFile("data.bin", "r");
FileRegion region = new DefaultFileRegion(
file.getChannel(), 0, file.length()
);
ctx.writeAndFlush(region); // uses sendfile() under the hood
// Data goes: disk → kernel → NIC — never copied to userspace
}
// Netty's pooled off-heap buffer
ByteBuf buf = ctx.alloc().directBuffer(1024); // from pool, off-heap
try {
buf.writeInt(42);
buf.writeBytes("hello".getBytes());
ctx.writeAndFlush(buf.retain()); // retain ref count before async write
} finally {
buf.release(); // return to pool — NOT freed to OS, reused
}
// CompositeByteBuf — logical view of multiple buffers (no copy)
CompositeByteBuf composite = ctx.alloc().compositeBuffer();
composite.addComponents(true, headerBuf, bodyBuf);
// headerBuf and bodyBuf remain separate in memory
// composite presents them as one contiguous view — zero copy
