skip to content

NIO & NIO.2

NIO and NIO.2: the buffer state model, channels, selectors for non-blocking multiplexing, the Path and Files API, and advanced facilities like memory mapping and async channels. Selectors are the part interviewers care about most, because they explain how scalable servers work.

part ofJavaoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

What is a Java NIO Buffer, and what do its four state markers (capacity, limit, position, mark) mean?

level: juniorimportance: must knowfreq 70%

answer

  1. 0 <= mark <= position <= limit <= capacity
  2. position = next slot, limit = the wall you can't cross
  3. capacity is fixed at allocation; never changes
  4. mark is a bookmark; reset() returns to it
  5. remaining() = limit - position

basics

~20 s

A Buffer is a fixed-size box that holds one kind of primitive value (like bytes). It tracks where you are with four numbers: capacity (total size), limit (how far you can go), position (the next slot to read/write), and mark (a saved spot you can jump back to).

solid answer

~40 s

A java.nio.Buffer is a fixed-capacity container for a single primitive type (ByteBuffer, IntBuffer, etc.). Its cursor state is four ints obeying 0 <= mark <= position <= limit <= capacity. Capacity is the total number of elements, fixed at allocation. Position is the index of the next element to be read or written; it advances on every get/put. Limit is the first index you may NOT touch — the boundary of accessible data. Mark is an optional remembered position you set with mark() and return to with reset(). The whole API works because reads and writes both move position toward limit, so the same object serves both phases as long as you reset limit and position correctly between them. Operations like flip(), clear(), and rewind() just reposition these markers.

go deeper

for a junior

Can name all four markers and state that capacity is fixed and position is the next slot to read/write.

for a middle

States the full invariant 0 <= mark <= position <= limit <= capacity and explains why both position and limit are needed for the fill-then-drain pattern.

for a senior

Connects the markers to flip/clear/rewind/compact, knows remaining()/hasRemaining(), and the exception behavior at boundaries.

for a principal

Reasons about the model's design trade-offs — single mutable cursor object, no thread safety, why fixed capacity simplifies native-memory mapping, and how this informs API design for zero-copy I/O.

## What a Buffer is In classic Java I/O you read and write through *streams* — you hand bytes to an `OutputStream` or pull them from an `InputStream`, one direction at a time, with the stream managing its own internal buffering invisibly. Java NIO (New I/O, package `java.nio`, since Java 1.4) inverts this: you work with an explicit, in-memory container called a **Buffer**, and channels move data between that buffer and the outside world (a file, a socket). A **Buffer** is a **fixed-size container for a single primitive type**. "Fixed-size" means once you allocate it, its total capacity never changes. "Single primitive type" means there is a separate subclass per primitive: `ByteBuffer`, `CharBuffer`, `IntBuffer`, `LongBuffer`, `FloatBuffer`, `DoubleBuffer`, `ShortBuffer` (and `MappedByteBuffer`). `ByteBuffer` is the one you use most, because channels read and write bytes. ## The four markers A buffer is conceptually an array plus four integer cursors. They always satisfy this invariant: ``` 0 <= mark <= position <= limit <= capacity ``` Let me define each, from the outside in: - **capacity** — the total number of elements the buffer can hold. Set once at allocation (`ByteBuffer.allocate(64)` → capacity 64) and **never changes**. It is the hard ceiling for every other marker. - **limit** — the index of the *first element you are NOT allowed to read or write*. Everything from `position` up to (but not including) `limit` is the accessible region. Initially `limit == capacity` (you may use the whole buffer). - **position** — the index of the *next* element a `get()` or `put()` will touch. It starts at 0 and **advances by one each time you read or write one element** (or by N for a bulk operation). When `position == limit`, there is nothing left in the current region. - **mark** — an *optional* bookmark. It is undefined ("unset") until you call `mark()`, which records the current `position`. Later, `reset()` sets `position` back to that marked value. The mark is discarded (becomes undefined) if `position` or `limit` is ever moved below it. ## Why limit and position both exist The genius (and the trap) of the model is that **the same buffer is used for both writing and reading**, and the markers tell the buffer which phase it is in. - While **filling** the buffer (e.g. `channel.read(buf)` writes data into it), `position` marks how much you have written and `limit` stays at capacity. - Then you **flip** with `flip()`: it sets `limit = position` ("the data ends here") and `position = 0` ("start reading from the front"). Now the accessible region is exactly the data you just wrote. - While **draining** the buffer (e.g. `channel.write(buf)` reads data out of it), `position` advances through the data until it hits `limit`. - `clear()` resets to `position = 0, limit = capacity` (ready to fill again — it does NOT erase the bytes). `rewind()` sets `position = 0` but leaves `limit` (re-read the same data). `compact()` moves unread bytes to the front and positions you to keep filling. ## remaining() and hasRemaining() `remaining()` returns `limit - position` — the number of elements still accessible. `hasRemaining()` is `position < limit`. These are how you loop over a buffer without ever touching the markers by hand. ## Edge cases and rules - Setting `limit` below the current `position` *also* drops `position` down to the new limit. Setting `position` (or `limit`) below `mark` discards the mark. - `reset()` throws `InvalidMarkException` if no mark is set. - Reading past `limit` (a relative `get()` when `!hasRemaining()`) throws `BufferUnderflowException`; writing past it throws `BufferOverflowException`. Absolute `get(index)`/`put(index, v)` ignore `position`/`limit` but still bounds-check against capacity (throwing `IndexOutOfBoundsException`). - Buffers are **not thread-safe**; the markers are plain mutable state. Once you internalize the invariant `0 <= mark <= position <= limit <= capacity` and the rule "position is the next slot, limit is the wall," every buffer method becomes predictable.

  • What does remaining() return, and in terms of which markers?
    It returns limit - position: the number of elements between the current position and the limit, i.e. how many you can still read or write.
  • What happens to the mark if you set the limit below it?
    The mark is discarded (becomes undefined). Any later reset() before re-marking throws InvalidMarkException.

saying these in an interview costs you the question

  • Thinking capacity can grow — buffers are fixed-size
  • Confusing limit with capacity (limit moves during read phase; capacity never does)
  • Believing clear() zeroes the data (it only resets markers)
  • Saying position points at the last element used (it points at the NEXT slot)
  • Assuming a mark always exists (it is undefined until mark() is called)

context

open as a page

After writing data into a ByteBuffer, what must you do before reading it back, and why?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Call flip() before reading. Writing moves the position forward; flip() sets the limit to where you stopped writing and resets position to 0, so reads start at the beginning and stop where the data ends.

open as a page

What is a Channel in Java NIO, and how does reading and writing through a Channel differ from a classic java.io stream?

level: juniorimportance: must knowfreq 60%

basics

~20 s

A Channel is a connection to an I/O source or sink (file, socket) that you read from and write to using ByteBuffer objects. Unlike a stream that moves one byte/array directly, a channel always exchanges data through a buffer, and many channels can read and write (bidirectional).

open as a page

What is the NIO.2 Path interface and how does it relate to the older java.io.File class?

level: juniorimportance: must knowfreq 68%

basics

~10 s

Path is the modern way (since Java 7) to represent a file or folder location. It replaces the old File class, working together with the Files helper class for cleaner, more reliable file operations.

open as a page

What is the difference between blocking and non-blocking I/O in Java NIO, and how do you switch a channel into non-blocking mode?

level: juniorimportance: must knowfreq 62%

basics

~10 s

In blocking I/O a read/write waits until data is ready, freezing the thread. Non-blocking I/O returns immediately, even if no data is available. You enable it with channel.configureBlocking(false).

open as a page

What are NIO.2 asynchronous channels, and how do they differ from blocking and non-blocking (selector-based) I/O in Java?

level: middleimportance: must knowfreq 55%

basics

~20 s

Asynchronous channels let you start an I/O operation and keep working immediately; you get the result later, either by checking a Future or by being called back when it finishes. You never block waiting for the read or write.

open as a page

What is the difference between ByteBuffer.allocate() and ByteBuffer.allocateDirect()?

level: middleimportance: must knowfreq 68%

basics

~20 s

allocate() makes a buffer backed by a normal Java byte array on the heap. allocateDirect() makes a buffer in off-heap native memory, which the OS can use for I/O without an extra copy, but it is slower to create.

open as a page

Explain flip(), clear(), rewind(), and compact() in terms of how each moves the Buffer's position, limit, and mark.

level: middleimportance: must knowfreq 65%

basics

~20 s

flip() gets a buffer ready to read what you just wrote: it sets limit to the current position, then position to 0. clear() gets it ready to write again: position 0, limit at capacity (data is left untouched). rewind() sets position to 0 to re-read. compact() keeps the unread bytes and lets you keep writing.

open as a page

What is the difference between clear() and compact() on a ByteBuffer, and when would you choose compact()?

level: middleimportance: must knowfreq 62%

basics

~20 s

clear() resets the buffer to write mode as if empty (position=0, limit=capacity) but does not erase data — so any unread bytes are lost. compact() keeps unread bytes, moves them to the front, and positions the cursor right after them so you can append more. Use compact() when you have only partially drained the buffer.

open as a page

Walk through the read(ByteBuffer) and write(ByteBuffer) semantics on a Channel, including buffer state transitions and partial transfers.

level: middleimportance: must knowfreq 55%

basics

~20 s

read(buffer) copies bytes from the channel into the buffer and moves the buffer's position forward by however many bytes were read. write(buffer) copies bytes from the buffer (between position and limit) out to the channel. Both return the count moved. You flip() the buffer to go from filling to draining, and may need to loop because a single call can transfer fewer bytes than you expect.

open as a page

What is a memory-mapped file in Java, and how do you create one with FileChannel.map()?

level: middleimportance: must knowfreq 58%

basics

~20 s

A memory-mapped file links a region of a file directly into memory so you read and write it like an array instead of calling read/write. In Java you get one by calling channel.map(mode, position, size), which returns a MappedByteBuffer.

open as a page

Explain Path.resolve, Path.relativize, and Path.normalize — what does each do and when would you use them?

level: middleimportance: must knowfreq 62%

basics

~20 s

resolve joins two paths (base + child). relativize finds the route from one path to another. normalize cleans up redundant . and .. segments. They are pure string-style operations that do not touch the disk.

open as a page

What is a Selector in Java NIO and how does it let one thread serve many channels?

level: middleimportance: must knowfreq 70%

basics

~20 s

A Selector is an object that watches many channels at once. You register channels with it, then call select() which sleeps until one or more channels are ready for I/O, so a single thread can handle many connections instead of one thread each.

open as a page

Walk through the full WatchService usage lifecycle, from creating the service to processing events. Why is calling reset() essential?

level: middleimportance: must knowfreq 55%

basics

~20 s

You create a WatchService, register a directory for the event kinds you want, then loop: block on take() until a key fires, drain its events with pollEvents(), handle each one, and call key.reset() to keep watching. Without reset() the key stops delivering new events.

open as a page

What does ByteBuffer.wrap(array) do, and how does it differ from ByteBuffer.allocate()?

level: juniorimportance: should knowfreq 45%

basics

~20 s

wrap() does not create new storage — it makes a buffer that reuses an existing byte array, so changes through the buffer change that array. allocate() creates a brand-new array of the given size for the buffer.

open as a page

What is the Java WatchService API and what problem does it solve?

level: juniorimportance: should knowfreq 45%

basics

~20 s

WatchService is a built-in Java tool (in java.nio.file) that tells your program when files or folders change. Instead of repeatedly scanning a directory yourself, you register it and get notified when files are created, modified, or deleted.

open as a page

Compare the two completion styles of async channels: Future-based versus CompletionHandler. When would you choose each?

level: middleimportance: should knowfreq 45%

basics

~20 s

With the Future style you start an operation and later poll or wait on a Future for the byte count. With the CompletionHandler style you pass a callback object whose completed() or failed() method runs automatically when the operation ends, so you never wait.

open as a page

What does ByteOrder control on a ByteBuffer, and why does it matter?

level: middleimportance: should knowfreq 40%

basics

~10 s

ByteOrder controls whether multi-byte values (int, long, etc.) are stored most-significant byte first (big-endian) or least-significant first (little-endian). It matters when reading/writing binary data that must match a file format, protocol, or another machine.

open as a page

What are ByteBuffer view buffers such as asIntBuffer(), and how do they relate to the underlying ByteBuffer?

level: middleimportance: should knowfreq 38%

basics

~20 s

A view buffer like asIntBuffer() reinterprets the same bytes of a ByteBuffer as a sequence of a larger type (ints). It shares the storage — no copy — so writes through the view change the underlying bytes, and it honors the ByteBuffer's byte order.

open as a page

How do mark() and reset() work on a Buffer, and what edge cases govern the mark?

level: middleimportance: should knowfreq 40%

basics

~20 s

mark() saves the current position so you can come back to it. reset() moves position back to that saved spot. If you call reset() without ever calling mark(), or after the mark was wiped out, you get an InvalidMarkException.

open as a page

Explain mark(), reset(), rewind(), and remaining()/hasRemaining(), and how they relate to position and limit.

level: middleimportance: should knowfreq 40%

basics

~20 s

mark() saves the current position; reset() jumps position back to that saved mark. rewind() sets position to 0 (keeping limit) so you can re-read. remaining() returns limit minus position — the number of elements left — and hasRemaining() is true when remaining() > 0.

open as a page

What is the difference between relative and absolute get/put on a Buffer, and how do they interact with position and limit?

level: middleimportance: should knowfreq 45%

basics

~20 s

Relative get()/put(value) operate at the current position and then advance it by one, bounded by limit. Absolute get(index)/put(index, value) take an explicit index, do not read or change position, and are bounded by capacity-ish range checks. Relative is for streaming; absolute is for random access.

open as a page

Name the main channel families and explain which support non-blocking mode and selectors, and which do not.

level: middleimportance: should knowfreq 40%

basics

~20 s

The main channel families are FileChannel (files), SocketChannel and ServerSocketChannel (TCP), and DatagramChannel (UDP). The socket and datagram channels can be set to non-blocking and registered with a Selector so one thread handles many connections. FileChannel is always blocking and cannot be used with a Selector.

open as a page

What is a FileLock in Java NIO, and how do shared vs. exclusive locks and lock() vs. tryLock() work?

level: middleimportance: should knowfreq 48%

basics

~20 s

A FileLock lets a program lock a file (or part of it) so processes coordinate access. A shared lock allows multiple readers; an exclusive lock allows only one writer. lock() waits until it can get the lock; tryLock() returns immediately, giving null if the lock isn't available.

open as a page

What are the MapMode options for FileChannel.map() (READ_ONLY, READ_WRITE, PRIVATE) and how do they differ?

level: middleimportance: should knowfreq 42%

basics

~20 s

READ_ONLY lets you only read the file. READ_WRITE lets you read and write, and changes go to the file. PRIVATE lets you write, but your changes stay in memory (copy-on-write) and are never saved back to the file.

open as a page

What are the key Files methods for creating, copying, moving, and deleting files and directories, and what options control their behavior?

level: middleimportance: should knowfreq 52%

basics

~10 s

Files.createFile/createDirectory/createDirectories make files and folders; Files.copy copies; Files.move moves or renames; Files.delete and deleteIfExists remove. Options like REPLACE_EXISTING, COPY_ATTRIBUTES, and ATOMIC_MOVE tweak how copy and move behave.

open as a page

What are SelectionKey interest ops, and how do interest sets and ready sets differ?

level: middleimportance: should knowfreq 48%

basics

~20 s

Interest ops (OP_ACCEPT, OP_CONNECT, OP_READ, OP_WRITE) say which events you want a channel watched for. The interest set is what you asked for; the ready set is which of those are actually ready right now after select().

open as a page

What is an AsynchronousChannelGroup, and why does its configuration matter for correctness and performance?

level: seniorimportance: should knowfreq 30%

basics

~10 s

An AsynchronousChannelGroup is the shared thread pool that runs async channel operations and invokes their completion callbacks. Channels can share one group. If you do not pick one, a default system-wide group is used.

open as a page

How does AsynchronousFileChannel work, and what is the significance of the explicit position parameter in its read/write methods?

level: seniorimportance: should knowfreq 35%

basics

~20 s

AsynchronousFileChannel reads and writes files without blocking the caller. Because many operations can be in flight at once, it has no single current file pointer, so every read and write must say exactly which byte offset to start at.

open as a page

What is the difference between a direct ByteBuffer and a heap (non-direct) ByteBuffer, and when would you choose each?

level: seniorimportance: should knowfreq 55%

basics

~20 s

A heap buffer stores its bytes in a normal Java array on the garbage-collected heap. A direct buffer stores them in native (off-heap) memory the OS can access directly. Direct buffers make channel I/O faster because the JVM can skip an extra copy, but they cost more to allocate and free.

open as a page

showing 1–30 of 46