Mastering Asynchronous Writes in Java NIO: Build a Reliable Write Queue

This article explains why direct writes to a Java NIO channel fail for large messages, introduces a write‑queue and OP_WRITE listener to handle partial writes, and provides a complete PacketWriter implementation that transforms synchronous writes into efficient asynchronous operations.

Lin is Dream
Lin is Dream
Lin is Dream
Mastering Asynchronous Writes in Java NIO: Build a Reliable Write Queue

In the previous article we implemented automatic handling of half‑packet and sticky‑packet issues when reading messages; this article now focuses on improving the write side, which previously wrote bytes directly to the channel and cannot handle large messages such as video files.

1. Network buffer limitations

When SocketChannel.write(ByteBuffer) is used in non‑blocking mode, the underlying socket’s send buffer (often 8KB‑16KB on Linux) may be full, causing the method to return 0. Continuing to write in this state wastes CPU, may cause packet loss, blocking, or even service crashes. The actual number of bytes written depends on the remaining space in the socket’s SO_SNDBUF and can vary from a few kilobytes to the full buffer size.

The return values of read() / write() have the following meanings: -1 – the peer has closed the connection; 0 – no data can be read or written at the moment; >0 – the number of bytes actually transferred.

Therefore, after a write operation fills the buffer, the correct approach is to stop writing, register a write‑ready listener, and resume writing the remaining data once the socket becomes writable again.

2. How to listen for write events

The solution is simple: cache the unwritten data in a queue and register the OP_WRITE event with the selector. When the socket becomes writable again, the selector triggers the write callback, the doWrite() method attempts to flush the remaining data, keeps the write interest if data is still pending, or removes it when the buffer is empty.

3. Design of write event handler

We introduce a PacketWriter class that encapsulates a Queue<ByteBuffer> for write buffering. It provides a send(ByteBuffer) method to enqueue data and immediately invoke doWrite(), and a doWrite() method that repeatedly writes the head of the queue until the socket cannot accept more data, at which point it registers the write event. Once a buffer is fully written, it is removed from the queue, and the write interest is cleared.

// Message write to channel
String msg = scanner.nextLine();
ByteBuffer buffer = this.ctx.codec().encode(msg);
this.ctx.channel().write(buffer);
public class PacketWriter {
    private final SocketChannel channel;
    private final Selector selector;
    private Queue<ByteBuffer> writeQueue = new ConcurrentLinkedQueue<>();

    public PacketWriter(SocketChannel channel, Selector selector) {
        this.channel = channel;
        this.selector = selector;
    }

    public void send(ByteBuffer buffer) throws IOException {
        // enqueue then write
        writeQueue.offer(buffer);
        doWrite();
    }

    public void doWrite() throws IOException {
        while (!writeQueue.isEmpty()) {
            ByteBuffer buf = writeQueue.peek();
            int len = channel.write(buf);
            // if socket buffer is full, register write event and return
            if (len == 0) {
                System.out.println("Current socket buffer is full, enable write listener");
                channel.register(selector, SelectionKey.OP_WRITE);
                return;
            }
            // if buffer fully written, remove it
            if (!buf.hasRemaining()) {
                writeQueue.poll();
            }
        }
        // all data written, cancel write interest
        SelectionKey key = channel.keyFor(selector);
        if (key != null) {
            key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
        }
    }
}

All places that write to the channel should now call PacketWriter.send() and enable the write listener, solving the problem of incomplete writes.

With this change, the mini Netty framework now supports both read half‑packet handling and cyclic write handling. Future articles will cover connection loss recovery, heartbeat mechanisms, and user‑friendly prompts to build a robust client connection model.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

nettyasynchronous-ioJava NIONon‑Blocking IOSocketChannelWrite Queue
Lin is Dream
Written by

Lin is Dream

Sharing Java developer knowledge, practical articles, and continuous insights into computer engineering.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.