Skip to content
TobussSystems in Practice

Java NIO & Netty Cheat Sheet

3 min read

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