Channels
A channel in Java NIO is an open connection to an I/O entity — a file, a socket, a pipe — that can be read from, written to, or both. Channels differ from streams in being bidirectional (a single FileChannel can both read and write), operating exclusively with ByteBuffers rather than individual bytes, supporting seeking and position-based access (FileChannel), and being capable of non-blocking mode (network channels). The channel hierarchy descends from java.nio.channels.Channel at the root; ReadableByteChannel and WritableByteChannel define the reading and writing contracts; ByteChannel combines both; SeekableByteChannel adds position and size access; FileChannel extends SeekableByteChannel with file-specific operations including memory mapping, file locking, and zero-copy transfers. For network I/O, SocketChannel, ServerSocketChannel, and DatagramChannel support both blocking and non-blocking modes and are registered with a Selector for multiplexed event-driven I/O. This entry covers the complete FileChannel API including map(), lock(), transferTo(), and transferFrom(); the blocking and non-blocking network channels; the Selector and SelectionKey model; Pipe channels for inter-thread communication; and the AsynchronousChannel group for callback-based or Future-based I/O.
FileChannel — Random Access, Transfers, Memory Mapping, and Locking
// ── FileChannel.open — preferred NIO.2 entry point ───────────────────
// Read-only:
try (FileChannel fc = FileChannel.open(Path.of("data.bin"),
StandardOpenOption.READ)) {
ByteBuffer buf = ByteBuffer.allocate(1024);
while (fc.read(buf) != -1) {
buf.flip();
processBuf(buf);
buf.clear();
}
}
// Read-write, create if missing:
try (FileChannel fc = FileChannel.open(Path.of("store.bin"),
StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
// Random access:
ByteBuffer record = ByteBuffer.allocate(64);
fc.read(record, 5 * 64); // read record at index 5 without changing position
record.flip();
int id = record.getInt(); // parse record
}
// ── Position, size, truncate ──────────────────────────────────────────
try (FileChannel fc = FileChannel.open(Path.of("data.bin"),
StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
System.out.println("Size: " + fc.size()); // file size in bytes
System.out.println("Position: " + fc.position()); // current read/write position
fc.position(1024); // seek to byte 1024
ByteBuffer buf = ByteBuffer.allocate(512);
int n = fc.read(buf); // read 512 bytes from position 1024
fc.position(fc.size()); // seek to end
buf.clear().putLong(System.currentTimeMillis()).flip();
fc.write(buf); // append 8 bytes at end
fc.truncate(4096); // truncate to 4096 bytes
System.out.println("After truncate: " + fc.size()); // 4096
}
// ── transferTo — zero-copy file serving ──────────────────────────────
try (FileChannel fileChannel = FileChannel.open(Path.of("file.html"));
SocketChannel socket = SocketChannel.open(new InetSocketAddress("host", 80))) {
long size = fileChannel.size();
long transferred = 0;
while (transferred < size) {
// Loop because transferTo may transfer less than requested:
transferred += fileChannel.transferTo(transferred, size - transferred, socket);
}
System.out.printf("Transferred %,d bytes (zero-copy)%n", transferred);
}
// ── Memory-mapped I/O ─────────────────────────────────────────────────
try (FileChannel fc = FileChannel.open(Path.of("database.bin"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
// Map the entire file into virtual memory:
MappedByteBuffer mapped = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());
mapped.order(ByteOrder.LITTLE_ENDIAN); // set byte order
// Read from virtual memory — no system call for cached pages:
mapped.position(0);
int magic = mapped.getInt();
int version = mapped.getShort() & 0xFFFF;
System.out.printf("Magic: 0x%X Version: %d%n", magic, version);
// Write to virtual memory — page becomes dirty, OS flushes to disk:
mapped.position(8);
mapped.putLong(System.currentTimeMillis()); // update timestamp in-place
// Force dirty pages to disk (equivalent to msync()):
mapped.force();
}
// ── File locking ──────────────────────────────────────────────────────
try (FileChannel fc = FileChannel.open(Path.of("shared.db"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
// Exclusive lock on entire file (blocking — waits until acquired):
try (FileLock lock = fc.lock()) {
System.out.println("Exclusive lock acquired");
// Only this process can hold an exclusive lock:
performExclusiveOperation(fc);
} // lock.release() called by close()
// Shared lock — multiple readers can hold simultaneously:
try (FileLock sharedLock = fc.lock(0, Long.MAX_VALUE, true)) {
System.out.println("Shared lock acquired");
readData(fc);
}
// tryLock — non-blocking, returns null if lock cannot be acquired:
FileLock maybeLock = fc.tryLock();
if (maybeLock != null) {
try {
updateData(fc);
} finally {
maybeLock.release();
}
} else {
System.out.println("File is locked by another process");
}
}SocketChannel, ServerSocketChannel, DatagramChannel, and Non-Blocking Mode
// ── SocketChannel blocking mode (simple, same as Socket) ────────────
try (SocketChannel sc = SocketChannel.open(
new InetSocketAddress("api.example.com", 443))) {
// Blocking mode by default — read/write behave like Socket streams
ByteBuffer request = ByteBuffer.wrap(
"GET / HTTP/1.0
Host: api.example.com
"
.getBytes(StandardCharsets.US_ASCII));
sc.write(request);
ByteBuffer response = ByteBuffer.allocate(8192);
while (sc.read(response) != -1) {
response.flip();
System.out.print(StandardCharsets.US_ASCII.decode(response));
response.clear();
}
}
// ── Non-blocking SocketChannel with Selector ─────────────────────────
Selector selector = Selector.open();
// Client connection (non-blocking):
SocketChannel client = SocketChannel.open();
client.configureBlocking(false);
client.connect(new InetSocketAddress("api.example.com", 80));
client.register(selector, SelectionKey.OP_CONNECT);
// Server (non-blocking):
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(8080));
server.register(selector, SelectionKey.OP_ACCEPT);
// Event loop:
while (true) {
int readyCount = selector.select(1000); // timeout in ms
if (readyCount == 0) { handleTimeout(); continue; }
Set<SelectionKey> selected = selector.selectedKeys();
for (Iterator<SelectionKey> it = selected.iterator(); it.hasNext(); ) {
SelectionKey key = it.next();
it.remove(); // MUST remove — selector won't do this
try {
if (key.isConnectable()) {
SocketChannel ch = (SocketChannel) key.channel();
if (ch.finishConnect()) { // complete the connection
ch.register(selector, SelectionKey.OP_READ);
}
}
else if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel incoming = ssc.accept(); // null if spurious wakeup
if (incoming != null) {
incoming.configureBlocking(false);
// Attach state to the key for later use:
incoming.register(selector, SelectionKey.OP_READ, new ClientState());
}
}
else if (key.isReadable()) {
SocketChannel ch = (SocketChannel) key.channel();
ClientState state = (ClientState) key.attachment();
ByteBuffer buf = ByteBuffer.allocate(4096);
int n = ch.read(buf);
if (n == -1) { // connection closed
key.cancel();
ch.close();
} else if (n == 0) { // no data yet (non-blocking) — safe to ignore
// Will get OP_READ again when data arrives
} else { // data available
buf.flip();
state.append(buf);
if (state.isRequestComplete()) {
key.interestOps(SelectionKey.OP_WRITE); // switch to write mode
state.prepareResponse();
}
}
}
else if (key.isWritable()) {
SocketChannel ch = (SocketChannel) key.channel();
ClientState state = (ClientState) key.attachment();
int written = ch.write(state.responseBuffer());
if (!state.responseBuffer().hasRemaining()) {
key.interestOps(SelectionKey.OP_READ); // done writing, back to read
}
}
} catch (IOException e) {
key.cancel();
key.channel().close();
}
}
}
// ── DatagramChannel — UDP ─────────────────────────────────────────────
try (DatagramChannel dc = DatagramChannel.open()) {
dc.bind(new InetSocketAddress(9999)); // bind to receive
ByteBuffer buf = ByteBuffer.allocate(65535);
SocketAddress sender = dc.receive(buf); // blocks until datagram arrives
buf.flip();
System.out.printf("Received %d bytes from %s%n", buf.limit(), sender);
// Echo back:
buf.flip(); // rewind to re-read same bytes
dc.send(buf, sender);
}
// UDP client:
try (DatagramChannel dc = DatagramChannel.open()) {
ByteBuffer msg = ByteBuffer.wrap("ping".getBytes(StandardCharsets.UTF_8));
dc.send(msg, new InetSocketAddress("localhost", 9999));
}Pipe, AsynchronousChannel, and Channel Utilities
// ── Pipe — inter-thread channel communication ─────────────────────────
Pipe pipe = Pipe.open();
Pipe.SinkChannel sink = pipe.sink();
Pipe.SourceChannel source = pipe.source();
// Producer thread:
Thread producer = new Thread(() -> {
try {
ByteBuffer data = ByteBuffer.wrap("Hello via Pipe!".getBytes(StandardCharsets.UTF_8));
sink.write(data);
sink.close(); // signals end of stream to source
} catch (IOException e) { e.printStackTrace(); }
}, "pipe-producer");
// Consumer thread:
Thread consumer = new Thread(() -> {
try {
ByteBuffer buf = ByteBuffer.allocate(1024);
while (source.read(buf) != -1) {
buf.flip();
System.out.println(StandardCharsets.UTF_8.decode(buf));
buf.clear();
}
} catch (IOException e) { e.printStackTrace(); }
}, "pipe-consumer");
producer.start(); consumer.start();
producer.join(); consumer.join();
// ── AsynchronousFileChannel — callback-based async I/O ────────────────
Path path = Path.of("large-file.bin");
ExecutorService callbackPool = Executors.newFixedThreadPool(4);
try (AsynchronousFileChannel afc = AsynchronousFileChannel.open(
path, Set.of(StandardOpenOption.READ), callbackPool)) {
ByteBuffer buf = ByteBuffer.allocate(65536);
// Callback style:
afc.read(buf, 0, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer bytesRead, Void att) {
buf.flip();
processBuffer(buf);
System.out.println("Read " + bytesRead + " bytes on "
+ Thread.currentThread().getName());
}
@Override
public void failed(Throwable exc, Void att) {
System.err.println("Read failed: " + exc.getMessage());
}
});
// Future style (blocks for result):
ByteBuffer buf2 = ByteBuffer.allocate(65536);
Future<Integer> future = afc.read(buf2, 65536); // read at offset 65536
int n = future.get(10, TimeUnit.SECONDS);
System.out.println("Future read: " + n + " bytes");
// Calling thread is NOT blocked between submit and get():
doOtherWork(); // runs while I/O is in progress
}
// ── Channels utility — bridge NIO and legacy IO ───────────────────────
// Channel → InputStream:
try (ReadableByteChannel rbc = FileChannel.open(Path.of("data.bin"));
InputStream is = Channels.newInputStream(rbc)) {
// Legacy code that needs InputStream:
legacyParser.parse(is);
}
// InputStream → Channel:
try (InputStream is = new URL("https://example.com/data").openStream();
ReadableByteChannel src = Channels.newChannel(is);
FileChannel dest = FileChannel.open(Path.of("download.bin"),
StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
dest.transferFrom(src, 0, Long.MAX_VALUE); // download file
}
// Channel → Reader with charset:
try (FileChannel fc = FileChannel.open(Path.of("text.txt"));
Reader reader = Channels.newReader(fc, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(reader)) {
br.lines().forEach(System.out::println);
}
// Channel → Writer with charset:
try (FileChannel fc = FileChannel.open(Path.of("output.txt"),
StandardOpenOption.CREATE, StandardOpenOption.WRITE);
Writer writer = Channels.newWriter(fc, StandardCharsets.UTF_8);
PrintWriter pw = new PrintWriter(writer)) {
pw.println("Written via NIO channel with charset encoding");
}
// ── Closing a channel interrupts blocked operations ────────────────────
FileChannel fc = FileChannel.open(Path.of("data.bin"));
ByteBuffer buf = ByteBuffer.allocate(1024);
Thread blocker = new Thread(() -> {
try {
fc.read(buf); // may block if data not available (rare for files, common for network)
} catch (AsynchronousCloseException e) {
System.out.println("Channel was closed while reading — clean shutdown");
} catch (IOException e) { e.printStackTrace(); }
});
blocker.start();
Thread.sleep(100);
fc.close(); // interrupts the blocked read in blocker thread → AsynchronousCloseException
blocker.join();