1. Future: “I’ll wait until it’s ready”
Asynchronous operations in Java are like sending a letter by mail: you don’t stand by the mailbox and wait for a reply; you go about your business. When the letter arrives, you simply get a notification. In code, however, you need to understand that the operation has completed and obtain the result or the error. In Java there are two approaches: via Future and via CompletionHandler.
With Future you receive a “receipt” — a promise of a result in the future. You can periodically check whether it’s ready, or wait for the result by calling get().
With CompletionHandler you don’t have to wait at all: you specify a handler in advance, and it will call itself — on success or on error.
How does it work?
When you call the asynchronous read() or write() method on AsynchronousFileChannel, you can get an object of type Future<Integer>. It’s like a ticket in an electronic queue: the operation has started, and at any moment you can ask, “So, is it ready?”.
Method signature
Future<Integer> read(ByteBuffer dst, long position)
or
Future<Integer> write(ByteBuffer src, long position)
- dst — the buffer to read data into.
- src — the buffer to write data from.
- position — the position in the file for reading/writing.
Example: asynchronous read with Future
Code that asynchronously reads the first 1024 bytes from a file and explicitly waits for the result:
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;
import java.io.IOException;
public class FutureReadExample {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// Start asynchronous read
Future<Integer> future = channel.read(buffer, 0);
// ... you can do other work here while the file is being read
// Wait for the operation to complete (blocking call!)
int bytesRead = future.get(); // May throw InterruptedException, ExecutionException
System.out.println("Bytes read: " + bytesRead);
// Proceed to reading from the buffer
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
} catch (Exception e) {
System.err.println("File read error: " + e);
}
}
}
- The future.get() method blocks the thread until the operation completes — it’s like “waiting by the phone” until someone calls.
- You can call future.isDone() to find out whether the operation has finished, and only then call get().
- If the operation finished with an error, get() will throw ExecutionException (with the cause inside).
When is Future convenient?
Future is good when you want to start a task and then, when convenient, wait for the result: read a file, make a request, compute something in the background. The approach is also convenient for launching several operations in parallel and then collecting the results.
But if you need true asynchrony without blocking and with automatic completion notifications, consider CompletionHandler.
2. CompletionHandler: “Call me when you’re done”
CompletionHandler is an interface that you implement and pass to the read() or write() method. When the operation completes (or an error occurs), Java will call your handler automatically. It’s like leaving your phone number: “Call me when it’s ready.”
Method signature
void read(ByteBuffer dst,
long position,
A attachment,
CompletionHandler<Integer, ? super A> handler)
- dst — the buffer for reading.
- position — the position in the file.
- attachment — an arbitrary object that will be passed to the handler (can be null or, for example, a file name).
- handler — your handler.
CompletionHandler interface
public interface CompletionHandler<V, A> {
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
}
- completed is called on success; result — number of bytes, attachment — your object.
- failed is called on error; exc — the exception.
Example: asynchronous read with CompletionHandler
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
public class CompletionHandlerReadExample {
public static void main(String[] args) throws IOException {
Path path = Path.of("example.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, 0, "example.txt", new CompletionHandler<Integer, String>() {
@Override
public void completed(Integer result, String attachment) {
System.out.println("File " + attachment + " read, bytes: " + result);
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
closeChannel();
}
@Override
public void failed(Throwable exc, String attachment) {
System.err.println("File read error " + attachment + ": " + exc);
closeChannel();
}
private void closeChannel() {
try {
channel.close();
} catch (IOException e) {
System.err.println("Channel close error: " + e);
}
}
});
// Important: main will finish before reading ends unless we keep the thread alive!
// In real applications the thread usually doesn't exit immediately (e.g., server, UI).
try {
Thread.sleep(500); // Give time for the async operation (for demo only!)
} catch (InterruptedException ignored) {}
}
}
We pass an anonymous handler to read() that knows what to do after completion. In completed we obtain the result and process it, and in failed we react to the error. Don’t forget to close the channel inside the handler so you don’t leave open resources.
There’s a nuance: the main method can finish earlier than the asynchronous operation, so the example includes Thread.sleep(500) — only to see the result. In real applications this trick is usually unnecessary.
When is it more convenient to use CompletionHandler?
CompletionHandler is good when you need true asynchrony without waits and blocking: you start an operation and describe what to do upon completion. This is critical for UI (JavaFX, Swing) so the interface doesn’t “freeze”, and useful on servers — threads don’t idle waiting; they’re used only when there’s work.
4. Comparison of Future and CompletionHandler
| Approach | Does it block the thread? | When to use? | Example scenario |
|---|---|---|---|
| Future | Yes (with get()) | Simple sequential processing | File copying, reports |
| CompletionHandler | No | Truly async, UI, server, parallel operations | Server, GUI, bulk I/O |
- Future is easier to understand, but requires blocking to obtain the result.
- CompletionHandler is a bit more complex, but provides true asynchrony and doesn’t block threads.
5. Practice: asynchronous file write with CompletionHandler
An example that asynchronously writes a string to a file and reports the outcome:
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class CompletionHandlerWriteExample {
public static void main(String[] args) throws IOException {
String text = "Hello, asynchronous world!";
ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
Path path = Path.of("async_output.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(
path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
channel.write(buffer, 0, path, new CompletionHandler<Integer, Path>() {
@Override
public void completed(Integer result, Path attachment) {
System.out.println("Successfully wrote " + result + " bytes to file " + attachment);
try {
channel.close();
} catch (IOException e) {
System.err.println("Channel close error: " + e);
}
}
@Override
public void failed(Throwable exc, Path attachment) {
System.err.println("File write error " + attachment + ": " + exc);
try {
channel.close();
} catch (IOException e) {
System.err.println("Channel close error: " + e);
}
}
});
// Keep main alive to see the result (for demo only)
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {}
}
}
GO TO FULL VERSION