Fundamentals 10 min read

Understanding Java IO: BIO, NIO, and AIO with Code Examples

This article explains Java's Input/Output mechanisms, detailing the differences between Blocking IO (BIO), Non‑blocking IO (NIO), and Asynchronous IO (AIO), their synchronization models, suitable scenarios, and provides practical code samples for reading and writing files using each approach.

Java Captain
Java Captain
Java Captain
Understanding Java IO: BIO, NIO, and AIO with Code Examples

IO (Input/Output) refers to the transfer of data between a computer's internal memory and external storage or devices. In Java, a set of APIs called Java IO enables developers to read and write external data such as files and sockets.

Java currently supports three IO models that coexist: BIO (Blocking IO), NIO (Non‑blocking IO), and AIO (Asynchronous IO). Each model differs in its blocking behavior and synchronization style.

Java BIO

BIO (Blocking IO) is a synchronous and blocking communication mode. It is simple and easy to use but has low concurrency and high resource consumption, making it suitable only for a small, fixed number of connections.

Java NIO

NIO (New IO) was introduced in Java SE 1.4 to improve network performance. It uses a non‑blocking synchronous model, processing data in blocks (buffers) rather than streams of bytes. This approach can achieve higher throughput but requires more complex programming.

Key differences:

Blocking vs. Non‑blocking: BIO blocks the thread until the operation completes; NIO returns immediately and the thread can perform other work.

Synchronous vs. Asynchronous: Both BIO and NIO are synchronous (the caller initiates the operation); AIO is asynchronous (the OS notifies the caller when the operation finishes).

Java AIO

AIO (Asynchronous IO) provides an asynchronous non‑blocking model. It introduces asynchronous channels that allow the OS to handle I/O operations and notify the application via callbacks when they complete.

Use Cases

BIO is appropriate for a small, fixed number of connections where simplicity is valued. NIO fits scenarios with many short‑lived connections, such as chat servers. AIO is best for a large number of long‑running connections, like file‑transfer or media‑streaming services.

Code Examples

Below are basic code snippets demonstrating how to perform file read/write using each model.

// Initializing an object
User1 user = new User1();
user.setName("hollis");
user.setAge(23);
System.out.println(user);
// Write object to file (BIO)
ObjectOutputStream oos = null;
try {
    oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
    oos.writeObject(user);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    IOUtils.closeQuietly(oos);
}
// Read object from file (BIO)
File file = new File("tempFile");
ObjectInputStream ois = null;
try {
    ois = new ObjectInputStream(new FileInputStream(file));
    User1 newUser = (User1) ois.readObject();
    System.out.println(newUser);
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
} finally {
    IOUtils.closeQuietly(ois);
    try { FileUtils.forceDelete(file); } catch (IOException e) { e.printStackTrace(); }
}
static void readNIO() {
    String pathname = "C:\\Users\\adew\\Desktop\\jd-gui.cfg";
    FileInputStream fin = null;
    try {
        fin = new FileInputStream(new File(pathname));
        FileChannel channel = fin.getChannel();
        int capacity = 100; // bytes
        ByteBuffer bf = ByteBuffer.allocate(capacity);
        int length;
        while ((length = channel.read(bf)) != -1) {
            bf.clear();
            byte[] bytes = bf.array();
            System.out.write(bytes, 0, length);
            System.out.println();
        }
        channel.close();
    } catch (FileNotFoundException e) { e.printStackTrace(); }
    catch (IOException e) { e.printStackTrace(); }
    finally { if (fin != null) try { fin.close(); } catch (IOException e) { e.printStackTrace(); } }
}

static void writeNIO() {
    String filename = "out.txt";
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(new File(filename));
        FileChannel channel = fos.getChannel();
        ByteBuffer src = Charset.forName("utf8").encode("你好你好你好你好你好");
        int length;
        while ((length = channel.write(src)) != 0) {
            System.out.println("写入长度:" + length);
        }
    } catch (FileNotFoundException e) { e.printStackTrace(); }
    catch (IOException e) { e.printStackTrace(); }
    finally { if (fos != null) try { fos.close(); } catch (IOException e) { e.printStackTrace(); } }
}
public class ReadFromFile {
    public static void main(String[] args) throws Exception {
        Path file = Paths.get("/usr/a.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
        ByteBuffer buffer = ByteBuffer.allocate(100_000);
        Future
result = channel.read(buffer, 0);
        while (!result.isDone()) { /* do other work */ }
        int bytesRead = result.get();
        System.out.println("Bytes read [" + bytesRead + "]");
    }
}

class WriteToFile {
    public static void main(String[] args) throws Exception {
        AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
            Paths.get("/asynchronous.txt"), StandardOpenOption.READ,
            StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        CompletionHandler
handler = new CompletionHandler
() {
            @Override public void completed(Integer result, Object attachment) {
                System.out.println("Attachment: " + attachment + " " + result + " bytes written");
                System.out.println("CompletionHandler Thread ID: " + Thread.currentThread().getId());
            }
            @Override public void failed(Throwable e, Object attachment) {
                System.err.println("Attachment: " + attachment + " failed with:");
                e.printStackTrace();
            }
        };
        fileChannel.write(ByteBuffer.wrap("Sample".getBytes()), 0, "First Write", handler);
        fileChannel.write(ByteBuffer.wrap("Box".getBytes()), 0, "Second Write", handler);
    }
}

In summary, choosing the right Java IO model depends on connection count, operation length, and performance requirements: BIO for simplicity with few connections, NIO for many short‑lived connections, and AIO for high‑concurrency, long‑lasting operations.

JavaasynchronousNIOFile I/OioBIOaio
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.