NIO channels & buffers in Java
ByteBuffer, channels, non-blocking I/O and selectors at a high level.
Channels and buffers
In NIO, a channel is a connection to a file or socket, and data never moves directly to or from it — it always goes through a buffer. FileChannel even supports memory mapping a file.
ByteBuffer buf = ByteBuffer.allocate(1024);
int n = channel.read(buf); // fill buf
buf.flip(); // now readThree markers
A buffer tracks capacity (fixed when allocated, never grows), position (the next index to read or write) and limit (where valid data ends). After allocate(8): position 0, limit 8, capacity 8.
flip(): from writing to reading
**flip() sets limit = position and position = 0 — "the data I just wrote is now ready to read". clear() or compact()** prepare it for writing again.
var buf = ByteBuffer.allocate(8);
buf.put((byte) 1).put((byte) 2);
// position 2, limit 8
buf.flip();
// position 0, limit 2After the flip
What does this print?
var buf = ByteBuffer.allocate(10);
buf.put(new byte[]{5, 6, 7});
buf.flip();
System.out.println(buf.remaining());
System.out.println(buf.get());7 03 510 5
Show the answer
3 — after flip, position 0 and limit 3, so 3 bytes remain. get() reads index 0: **5**.
Forgetting flip()
Without flip(), position still points after your data. get() reads the next untouched slot — a 0, not your 7. This is the classic NIO bug.
var buf = ByteBuffer.allocate(4);
buf.put((byte) 7); // position 1
buf.remaining(); // 3 unused slots
buf.get(); // 0: reads index 1!Selectors: one thread, many channels
Socket channels can be non-blocking. Register many of them with a **Selector**, and select() reports which ones are ready to read or write — so one thread can serve thousands of connections.
Who uses NIO
High-performance servers and frameworks — Netty, Tomcat's NIO connector, Kafka — are built on channels and selectors. Most app developers use them indirectly, but buffer flip bugs and memory-mapped files show up in performance-critical code.
Key takeaways
- Buffer: capacity, position, limit
- flip(): limit = position, position = 0 — ready to read
- clear()/compact() prepare the buffer for writing again
- Selector: one thread, many non-blocking channels
NIO stands for "New I/O" — introduced in Java 1.4 back in 2002, so the "new" API is now over twenty years old.
Practice questions
What does this print?
ByteBuffer buf = ByteBuffer.allocate(8);
buf.put((byte) 1).put((byte) 2);
System.out.println(
buf.position() + " " + buf.limit());
buf.flip();
System.out.println(
buf.position() + " " + buf.limit());- 2 8 0 2
- 2 8 0 8
- 0 8 2 8
- 2 2 0 2
Check your answer
2 8 0 2. After two puts, position is 2 and the limit is still the capacity, 8. flip() sets limit to 2 (the end of the data) and position back to 0.
What does this print?
ByteBuffer buf = ByteBuffer.allocate(4);
buf.put((byte) 7);
System.out.println(buf.remaining());
System.out.println(buf.get());- 1 7
- 3 0
- 3 7
- Throws BufferUnderflowException
Check your answer
3 0. Without flip(), position is 1, so remaining() counts the 3 unused slots and get() reads index 1 — an untouched 0, not the 7. Forgetting flip() is the classic NIO bug.