Skip to main content
  1. Java NIO (New I/O)/

SelectionKey

7 mins

The SelectionKey class represents a channel’s registration with a selector, maintaining interest and ready operation sets along with attachment capabilities.

Source Code #

View Source on GitHub

Core Implementation #

public abstract class SelectionKey {
    protected SelectionKey() { }
    
    public abstract SelectableChannel channel();
    public abstract Selector selector();
    public abstract boolean isValid();
    public abstract void cancel();
    
    public abstract int interestOps();
    public abstract SelectionKey interestOps(int ops);
    
    public int interestOpsOr(int ops) {
        synchronized (this) {
            int oldVal = interestOps();
            interestOps(oldVal | ops);
            return oldVal;
        }
    }
    
    public int interestOpsAnd(int ops) {
        synchronized (this) {
            int oldVal = interestOps();
            interestOps(oldVal & ops);
            return oldVal;
        }
    }
    
    public abstract int readyOps();
    
    // Operation bits
    public static final int OP_READ = 1 << 0;
    public static final int OP_WRITE = 1 << 2;
    public static final int OP_CONNECT = 1 << 3;
    public static final int OP_ACCEPT = 1 << 4;
    
    // Convenience methods
    public final boolean isReadable() {
        return (readyOps() & OP_READ) != 0;
    }
    
    public final boolean isWritable() {
        return (readyOps() & OP_WRITE) != 0;
    }
    
    public final boolean isConnectable() {
        return (readyOps() & OP_CONNECT) != 0;
    }
    
    public final boolean isAcceptable() {
        return (readyOps() & OP_ACCEPT) != 0;
    }
    
    // Attachments
    private static final VarHandle ATTACHMENT = MhUtil.findVarHandle(
            MethodHandles.lookup(), "attachment", Object.class);
    
    private volatile Object attachment;
    
    public final Object attach(Object ob) {
        return ATTACHMENT.getAndSet(this, ob);
    }
    
    public final Object attachment() {
        return attachment;
    }
}

Implementation Details #

SelectionKeyImpl Concrete Class #

The platform-specific implementation extends AbstractSelectionKey:

public final class SelectionKeyImpl extends AbstractSelectionKey {
    private static final VarHandle INTERESTOPS = ConstantBootstraps.fieldVarHandle(
            MethodHandles.lookup(), "interestOps", VarHandle.class, 
            SelectionKeyImpl.class, int.class);
    
    private final SelChImpl channel;
    private final SelectorImpl selector;
    
    private volatile int interestOps;
    private volatile int readyOps;
    
    // Platform-specific fields
    private int registeredEvents;   // Events registered in kernel
    private volatile boolean reset; // Needs re-registration
    private int index;              // Index in pollfd array
    private int lastPolled;         // Last poll timestamp
    
    SelectionKeyImpl(SelChImpl ch, SelectorImpl sel) {
        channel = ch;
        selector = sel;
    }
    
    @Override
    public SelectableChannel channel() {
        return (SelectableChannel)channel;
    }
    
    @Override
    public Selector selector() {
        return selector;
    }
    
    @Override
    public int interestOps() {
        ensureValid();
        return interestOps;
    }
    
    @Override
    public SelectionKey interestOps(int ops) {
        ensureValid();
        if ((ops & ~channel().validOps()) != 0)
            throw new IllegalArgumentException();
        int oldOps = (int) INTERESTOPS.getAndSet(this, ops);
        if (ops != oldOps) {
            selector.setEventOps(this); // Notify selector of change
        }
        return this;
    }
    
    @Override
    public int readyOps() {
        ensureValid();
        return readyOps;
    }
    
    // Internal NIO methods (not affected by validity checks)
    public void nioReadyOps(int ops) {
        readyOps = ops;
    }
    
    public int nioReadyOps() {
        return readyOps;
    }
    
    public SelectionKey nioInterestOps(int ops) {
        if ((ops & ~channel().validOps()) != 0)
            throw new IllegalArgumentException();
        interestOps = ops;
        selector.setEventOps(this);
        return this;
    }
    
    public int nioInterestOps() {
        return interestOps;
    }
    
    int translateInterestOps() {
        return channel.translateInterestOps(interestOps);
    }
    
    boolean translateAndSetReadyOps(int ops) {
        return channel.translateAndSetReadyOps(ops, this);
    }
    
    boolean translateAndUpdateReadyOps(int ops) {
        return channel.translateAndUpdateReadyOps(ops, this);
    }
    
    void registeredEvents(int events) {
        this.registeredEvents = events;
    }
    
    int registeredEvents() {
        return registeredEvents;
    }
    
    void reset() {
        reset = true;
        selector.setEventOps(this);
        selector.wakeup();
    }
    
    boolean getAndClearReset() {
        boolean r = reset;
        if (r) reset = false;
        return r;
    }
}

Operation Set Bits #

OperationBit ValueChannel SupportDescription
OP_READ1 (1«0)SocketChannel, DatagramChannel, Pipe.SourceChannelChannel ready for reading
OP_WRITE4 (1«2)SocketChannel, DatagramChannel, Pipe.SinkChannelChannel ready for writing
OP_CONNECT8 (1«3)SocketChannelConnection established or failed
OP_ACCEPT16 (1«4)ServerSocketChannelIncoming connection available

Attachment Mechanism #

Selection keys support attaching arbitrary application data using a VarHandle-based atomic attachment mechanism:

private static final VarHandle ATTACHMENT = MhUtil.findVarHandle(
        MethodHandles.lookup(), "attachment", Object.class);
private volatile Object attachment;

public final Object attach(Object ob) {
    return ATTACHMENT.getAndSet(this, ob);
}

public final Object attachment() {
    return attachment;
}

Characteristics:

  • Atomic updates: getAndSet ensures thread-safe attachment changes
  • Single attachment: Only one object can be attached at a time
  • Null allowed: null can be attached to clear previous attachment
  • Volatile visibility: Changes are immediately visible to other threads

Validity and Cancellation #

A selection key remains valid until:

  1. Explicit cancellation via cancel() method
  2. Channel closure
  3. Selector closure

Cancellation adds the key to the selector’s cancelled-key set for removal during the next selection operation. The isValid() method returns false once cancelled.

private void ensureValid() {
    if (!isValid())
        throw new CancelledKeyException();
}

Thread Safety #

  • SelectionKey operations: Thread-safe for concurrent access
  • Interest set changes: Atomic via VarHandle operations
  • Ready set updates: Only modified by selector during selection operations
  • Attachment: Atomic getAndSet ensures consistency

Platform-Specific Fields #

Different selector implementations use platform-specific fields:

FieldPurposeUsed By
registeredEventsEvents registered in kernel (epoll/poll)EPollSelectorImpl, PollSelectorImpl
indexIndex in native pollfd arrayPollSelectorImpl
lastPolledTimestamp of last selectionMonitoring/debugging
resetFlag indicating need for re-registrationAll selectors

Common Usage Patterns #

Basic Key Processing #

Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

// Process ready events
if (key.isReadable()) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int bytesRead = channel.read(buffer);
    if (bytesRead == -1) {
        key.cancel();
        channel.close();
    }
}

// Update interest ops
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

// Attach application data
ConnectionState state = new ConnectionState();
key.attach(state);

// Later retrieve attachment
ConnectionState attached = (ConnectionState) key.attachment();

State Management with Attachments #

class ConnectionHandler {
    private SelectionKey key;
    
    public void handleRead() {
        ByteBuffer buffer = ByteBuffer.allocate(4096);
        SocketChannel channel = (SocketChannel) key.channel();
        int bytesRead = channel.read(buffer);
        
        if (bytesRead == -1) {
            key.cancel();
            channel.close();
            return;
        }
        
        buffer.flip();
        processData(buffer);
        
        // Switch to write mode when response ready
        key.interestOps(SelectionKey.OP_WRITE);
    }
    
    public void handleWrite() {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer response = getResponse();
        channel.write(response);
        
        if (!response.hasRemaining()) {
            // Switch back to read mode
            key.interestOps(SelectionKey.OP_READ);
        }
    }
}

// Register with attachment
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
key.attach(new ConnectionHandler(key));

Interest Ops Management #

// Safe interest ops updates
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);

// Using atomic operations (Java 11+)
int previousOps = key.interestOpsOr(SelectionKey.OP_WRITE);
int clearedOps = key.interestOpsAnd(~SelectionKey.OP_READ);

// Batch interest ops changes
int newOps = SelectionKey.OP_READ;
if (shouldWrite) newOps |= SelectionKey.OP_WRITE;
if (shouldAccept) newOps |= SelectionKey.OP_ACCEPT;
key.interestOps(newOps);

Key Validation Patterns #

// Always check validity before operations
if (key.isValid()) {
    if (key.isReadable()) {
        // Process read
    }
} else {
    // Clean up resources
    cleanup(key);
}

// Ensure validity in critical sections
try {
    key.interestOps(SelectionKey.OP_WRITE);
} catch (CancelledKeyException e) {
    // Key was cancelled concurrently
    handleCancelledKey(key);
}

Best Practices #

  1. Check Key Validity: Always call isValid() before key operations
  2. Use Atomic Interest Ops: Prefer interestOpsOr() and interestOpsAnd() for thread-safe updates
  3. Attach Lightweight Objects: Keep attachments small and simple
  4. Clear Attachments: Set attachment to null when no longer needed
  5. Handle Cancellation: Always be prepared for CancelledKeyException
  6. Avoid Interest Ops Accumulation: Clear bits before setting new interest ops
  7. Use Ready Ops Hints: Ready ops are hints, not guarantees
  8. Close Channels Properly: Channel closure automatically cancels keys
  9. Monitor Key Count: Large numbers of keys impact selector performance
  10. Avoid Blocking in Key Handlers: Keep key processing fast to prevent selector starvation

Common Pitfalls #

  1. Ignoring CancelledKeyException: Not handling concurrent key cancellation
  2. Assuming Ready Ops Accuracy: Ready ops may become stale immediately after selection
  3. Attachment Memory Leaks: Forgetting to clear attachments when keys are cancelled
  4. Interest Ops Race Conditions: Concurrent interest ops updates without atomic operations
  5. Blocking in Key Processing: Causing selector to stall for other channels
  6. Not Removing Invalid Keys: Keeping references to cancelled keys
  7. Incorrect Interest Ops Combinations: Setting unsupported ops for channel type
  8. Forgetting to Cancel Keys: Leading to selector resource leaks
  9. Attachment Type Safety: Not casting attachments safely
  10. Ignoring Platform Limits: Exceeding platform-specific key limits

Performance Considerations #

Interest Ops Updates #

  • Atomic operations: interestOpsOr() and interestOpsAnd() use VarHandle for atomic updates
  • Selector notification: Interest ops changes trigger selector wakeup
  • Batch updates: Minimize interest ops changes to reduce selector overhead

Attachment Overhead #

  • Memory footprint: Each attachment adds to heap usage
  • GC pressure: Large attachments increase garbage collection
  • Thread-local alternatives: Consider thread-local storage for heavy state

Key Lifecycle Management #

  • Creation overhead: Key creation involves allocation and registration
  • Cancellation cost: Cancelled keys are processed during selection operations
  • Reuse patterns: Reuse keys where possible (though not directly supported)

Selector #

  • Multiplexes multiple selectable channels
  • Creates and manages selection keys
  • Selector Details

SelectableChannel #

SocketChannel #

  • TCP socket channel supporting OP_READ, OP_WRITE, OP_CONNECT
  • Most common channel type used with selection keys
  • SocketChannel Details

ServerSocketChannel #

DatagramChannel #

  • UDP datagram channel supporting OP_READ, OP_WRITE
  • Connectionless channel with selection support
  • DatagramChannel Details

Pipe #

  • Intra-JVM communication channels
  • Pipe.SourceChannel supports OP_READ
  • Pipe.SinkChannel supports OP_WRITE
  • Pipe Details