skip to content

Non-blocking I/O & Selectors

A Selector lets one thread watch many non-blocking channels and act only on ready ones, which is the Reactor pattern behind Netty and every scalable Java server. Interviewers ask how you would serve ten thousand connections without ten thousand threads.

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

questions

5

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%

answer

  1. configureBlocking(false) = the on switch
  2. Blocking parks the thread; non-blocking returns immediately
  3. read() may return 0, write() may be partial
  4. One thread serves many channels
  5. FileChannel is NOT selectable

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).

solid answer

~40 s

Blocking I/O (the classic InputStream/Socket model and NIO's default) makes a thread wait inside a read or write call until the operation can make progress, so one thread is tied up per connection. Non-blocking I/O, enabled by calling channel.configureBlocking(false) on a SelectableChannel such as SocketChannel or ServerSocketChannel, makes those calls return immediately: read() may return 0 bytes, write() may write fewer bytes than requested, and accept()/connect() may return null/false. Because the thread is never parked, a single thread can manage many channels. The trade-off is that you must repeatedly check readiness (ideally via a Selector rather than busy-polling) and handle partial reads and writes yourself. Non-blocking mode is the foundation for the Reactor pattern and scalable servers.

code

java · 10 lines
java
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);   // non-blocking: accept() may return null

SocketChannel client = server.accept(); // returns immediately, possibly null
if (client != null) {
    client.configureBlocking(false);
    ByteBuffer buf = ByteBuffer.allocate(1024);
    int n = client.read(buf);          // 0 = nothing ready, -1 = closed
}

go deeper

for a junior

Knows blocking waits and freezes the thread, non-blocking returns immediately, and that configureBlocking(false) is the switch.

for a middle

Explains the per-operation return contracts (read 0/-1, partial write, null accept) and why one thread can then serve many connections.

for a senior

Connects it to the thread-per-connection cost (C10k), partial-write handling, and that a Selector — not busy-polling — is the right readiness mechanism.

for a principal

Weighs non-blocking+Reactor against virtual-thread blocking, latency/back-pressure implications, and when each model is the right architectural choice.

## What "I/O" and "blocking" mean **I/O** (input/output) is moving bytes between your program and something external: a network socket, a file, a pipe. A **channel** in Java NIO (`java.nio.channels`) is an object representing an open connection to such a resource — e.g. `SocketChannel` (a TCP connection), `ServerSocketChannel` (a listening socket that accepts connections), or `DatagramChannel` (UDP). **Blocking** means a method call does not return control to your code until it has finished its work. With blocking I/O, if you call `read()` and no data has arrived from the network yet, the calling **thread** (a unit of execution the OS schedules) is suspended — it sits idle, consuming a thread but doing nothing — until at least some data arrives. This is the model of the old `java.io` streams and is also the *default* for NIO channels. ## The problem blocking causes A server that uses blocking I/O typically dedicates **one thread per connection**: the thread calls `read()`, blocks until a request arrives, processes it, writes a reply, loops. This is simple, but each thread costs memory (a stack, often ~256KB–1MB) and the OS scheduler slows down with thousands of threads. Serving 10,000 idle-but-open connections would need 10,000 mostly-sleeping threads — wasteful. This is historically called the **C10k problem**. ## What non-blocking I/O changes You switch a channel into non-blocking mode by calling: ```java channel.configureBlocking(false); ``` Now the channel's operations return *immediately*, whether or not they could do useful work: - `read(buffer)` returns the number of bytes read, which may be **0** (nothing was ready) or **-1** (end of stream / peer closed). - `write(buffer)` may write **fewer bytes than the buffer holds** (a *partial write*) — the socket's send buffer was full. - `ServerSocketChannel.accept()` returns **null** if no client is waiting. - `SocketChannel.connect()` returns **false** if the TCP handshake hasn't completed yet (you later call `finishConnect()`). The thread is therefore never parked inside an I/O call, so **one thread can service many channels** by checking each in turn. ## The new responsibilities Non-blocking mode pushes work onto you: 1. **Readiness checking.** You shouldn't loop calling `read()` on every channel forever (that's *busy-polling* and burns CPU). The proper tool is a **`Selector`**, which lets one thread wait efficiently until *any* of many channels becomes ready (covered in its own question). 2. **Partial reads/writes.** Because `write()` may not write everything, you must keep the leftover bytes and try again when the channel is writable. Likewise a single `read()` may give you only part of a message, so you must accumulate bytes until you have a complete protocol unit. ## When to use which - **Blocking** is simpler and perfectly fine for a small number of connections or simple client code. Project Loom's **virtual threads** (Java 21+) even make the thread-per-connection blocking style cheap again. - **Non-blocking + Selector** shines when one (or a few) threads must scale to very many concurrent connections — the model behind frameworks like **Netty** and servers built on the **Reactor pattern**. ## Key API facts to remember - `configureBlocking(false)` is the switch; it's on `SelectableChannel`. - `FileChannel` is **not** a `SelectableChannel` — you cannot put a plain file channel into non-blocking/selector mode. - A channel must be non-blocking before it can be registered with a `Selector`.

  • After configureBlocking(false), what does read() return when no data is available?
    0 — it returns immediately having read zero bytes (as opposed to -1, which means end-of-stream/peer closed).
  • Can you register a FileChannel with a Selector?
    No. FileChannel is not a SelectableChannel, so it can't be made non-blocking or registered with a Selector. Selectors work with socket-style channels.

saying these in an interview costs you the question

  • Thinking non-blocking means faster per-operation — it means the thread isn't parked, not that bytes move faster
  • Assuming write() always writes the whole buffer
  • Believing FileChannel can be put in non-blocking mode for a Selector
  • Busy-looping read() on every channel instead of using a Selector

context

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

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 are the common pitfalls when writing a Selector-based server, and how do you avoid them?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Main pitfalls: forgetting to remove handled keys from selectedKeys() (causes reprocessing), leaving OP_WRITE always on (busy spin), ignoring partial reads/writes, blocking inside the loop, and not handling -1 from read() (peer closed). Fix each by following the correct NIO idioms.

open as a page

Explain the Reactor pattern and how Java NIO Selectors implement it. Why do servers like Netty use it?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The Reactor pattern uses one (or a few) event-loop threads that wait for I/O readiness and dispatch each ready event to a handler. Java's Selector is exactly this: select() detects readiness, and you route OP_READ/OP_WRITE/OP_ACCEPT to the right code. It lets a server handle many connections with few threads.

open as a page