Java I/O and NIO



Java I/O (Input/Output) is a set of APIs that enables reading and writing of data (files, streams, console, network, etc.). It supports both byte-based and character-based data.


Java NIO (New Input/Output) is a modern alternative introduced in Java 1.4 that supports buffer-oriented, non-blocking I/O with advanced features like channels, selectors, and memory-mapped files.





History


Java Version

Feature/Improvement

Description

Java 1.0

java.io package

Basic stream classes: InputStream, OutputStream, Reader, Writer

Java 1.1

Serialization (Serializable)

Allows saving objects to a stream and reconstructing them later

Java 1.4

New I/O (NIO) - java.nio

Introduced Buffer, Channel, Selector for faster, non-blocking I/O

Java 7

AutoCloseable with try-with-resources

Simplified resource management using try-with-resources (corrected from Java 5)

Java 7

NIO.2 - java.nio.file

Added Path, Files, DirectoryStream, WatchService

Java 8

Stream APIs with I/O

I/O operations like Files.lines(Path) return Stream for functional processing

Java 9

Reactive Streams (Flow API)

Introduced java.util.concurrent.Flow for asynchronous processing

Java 11+

Minor updates

Enhancements to file APIs (Files.readString, Files.writeString, etc.)





Java I/O (Classic) Hierarchy

java.io
|
|── InputStream (abstract class)           - Root byte input stream
|   |                                       Reads raw binary data (bytes)
|   |
|   |── FileInputStream                    - Reads bytes from a file
|   |── ByteArrayInputStream               - Reads bytes from a byte array
|   |── BufferedInputStream                - Adds buffering for efficient reading
|   |── ObjectInputStream                  - Deserializes objects from a stream
|   |── PipedInputStream                   - Reads from a connected PipedOutputStream
|   |── SequenceInputStream                - Reads multiple streams sequentially
|   |
|   └── FilterInputStream                  - Base class for filtered input streams
|       |
|       └── DataInputStream                - Reads Java primitive data types
|
|── OutputStream (abstract class)          - Root byte output stream
|   |                                       Writes raw binary data (bytes)
|   |
|   |── FileOutputStream                   - Writes bytes to a file
|   |── ByteArrayOutputStream              - Writes bytes to a byte array
|   |── BufferedOutputStream               - Adds buffering for efficient writing
|   |── ObjectOutputStream                 - Serializes objects to a stream
|   |── PipedOutputStream                  - Writes to a connected PipedInputStream
|   |
|   └── FilterOutputStream                 - Base class for filtered output streams
|       |
|       |── DataOutputStream               - Writes Java primitive data types
|       └── PrintStream                    - Prints formatted representations
|
|── Reader (abstract class)                - Root character input stream
|   |                                       Reads Unicode characters
|   |
|   |── StringReader                       - Reads characters from a String
|   |── CharArrayReader                    - Reads characters from a char array
|   |── BufferedReader                     - Adds buffering for efficient reading
|   |
|   └── InputStreamReader                  - Converts bytes to characters
|       |
|       └── FileReader                     - Reads characters from a text file
|
|── Writer (abstract class)                - Root character output stream
|   |                                       Writes Unicode characters
|   |
|   |── StringWriter                       - Writes characters to a String buffer
|   |── CharArrayWriter                    - Writes characters to a char array
|   |── BufferedWriter                     - Adds buffering for efficient writing
|   |── PrintWriter                        - Prints formatted text
|   |
|   └── OutputStreamWriter                 - Converts characters to bytes
|       |
|       └── FileWriter                     - Writes characters to a text file
|
|── File                                   - Represents file and directory pathnames
|
|── RandomAccessFile                       - Supports reading and writing at any file position
|
|── Serializable                           - Marker interface for object serialization
|
|── Externalizable                         - Custom serialization interface
|   |
|   └── Serializable
|
|── DataInput                              - Interface for reading primitive data types
|
└── DataOutput                             - Interface for writing primitive data types


Note: InputStream and OutputStream work with bytes (binary data), 
while Reader and Writer work with characters (text data). 
This distinction is the foundation of Java Classic I/O.

InputStream Hierarchy (Byte Input)


Class

One-liner Definition

Real-Time Use Case

Code Example

InputStream

Base class for reading raw byte streams.

Superclass for all byte-based input streams.

ByteArrayInputStream                                            

Reads from a byte array as if it were a stream.

Read image/file loaded in memory.

new ByteArrayInputStream(byteArray)

FileInputStream

Reads bytes from a file.

Load PDF/image file for processing.

new FileInputStream("file.pdf")

BufferedInputStream

Buffers input for efficient reading.

Load large files faster.

new BufferedInputStream(new FileInputStream("log.txt"))

DataInputStream

Reads Java primitives (int, float) from stream.

Reading binary file formats like .class files.

new DataInputStream(new FileInputStream("data.bin"))

ObjectInputStream        

Deserializes Java objects from a stream.

Read saved state of a Java object.

new ObjectInputStream(new FileInputStream("user.ser"))


OutputStream Hierarchy (Byte Output)


Class

One-liner Definition

Real-Time Use Case

Code Example

OutputStream

Base class for writing raw byte streams.

Superclass for all byte-based output streams.

ByteArrayOutputStream                    

Writes to a byte array in memory.

Combine multiple byte arrays into one.

new ByteArrayOutputStream()

FileOutputStream

Writes bytes to a file.

Save a downloaded image.

new FileOutputStream("image.jpg")

BufferedOutputStream        

Buffers output for performance.

Writing logs or large file.

new BufferedOutputStream(new FileOutputStream("log.txt"))

DataOutputStream

Writes Java primitives (int, float).

Save structured binary data.

new DataOutputStream(new FileOutputStream("numbers.dat"))

ObjectOutputStream        

Serializes Java objects to a stream.

Save app state to disk.

new ObjectOutputStream(new FileOutputStream("user.ser"))


Reader Hierarchy (Character Input)


Class

One-liner Definition

Real-Time Use Case

Code Example

Reader (abstract class)

Base class to read character data.

Superclass for all character input readers.

CharArrayReader

Reads from a char array as a stream.

Parse in-memory XML config.

new CharArrayReader(charArray)

StringReader

Reads characters from a string.

Simulate file input for testing.

new StringReader("test input")

FileReader

Reads characters from a file.

Read plain text file.

new FileReader("input.txt")

BufferedReader

Reads text efficiently with buffer.

Read large text files line-by-line.

new BufferedReader(new FileReader("notes.txt"))

InputStreamReader

Converts bytes to characters.

Read UTF-8 encoded file.

new InputStreamReader(new FileInputStream("utf.txt"), "UTF-8")

LineNumberReader    

Keeps track of line numbers.

Parse code files with line numbers.

new LineNumberReader(new FileReader("code.java"))


Writer Hierarchy (Character Output)


Class

One-liner Definition

Real-Time Use Case

Code Example

Writer (abstract class)

Base class to write character data.

Superclass for all character writers.

CharArrayWriter

Writes to a character array.

Create temporary in-memory content.

new CharArrayWriter()

StringWriter

Writes to a string buffer.

Build a string with writer methods.

new StringWriter()

FileWriter

Writes characters to file.

Save user notes in text format.

new FileWriter("output.txt")

BufferedWriter

Buffers characters for efficient writing.

Writing logs or large files.

new BufferedWriter(new FileWriter("log.txt"))

OutputStreamWriter    

Converts characters to bytes.

Write with specified encoding (e.g. UTF-8).

new OutputStreamWriter(new FileOutputStream("out.txt"), "UTF-8")

PrintWriter

Prints formatted text (like System.out).

Log formatted messages easily.

new PrintWriter("log.txt")


Other Classes


Class/Interface

One-liner Definition

Real-Time Use Case

Code Example

File (class)

Represents a file or directory.

Check file properties, list directory.

new File("data.csv").exists()

RandomAccessFile    

Read/write to file at random positions.

Implementing file-based database.

new RandomAccessFile("log.txt", "rw")

Serializable

Marks object to be serializable.

Persist or transfer objects.

class User implements Serializable





Java NIO (New I/O) Hierarchy

java.nio
|
|── Buffer (abstract class)                - Core container for data transfer
|   |
|   |── ByteBuffer                         - Stores byte data
|   |── CharBuffer                         - Stores char data
|   |── ShortBuffer                        - Stores short values
|   |── IntBuffer                          - Stores int values
|   |── LongBuffer                         - Stores long values
|   |── FloatBuffer                        - Stores float values
|   └── DoubleBuffer                       - Stores double values
|
|── java.nio.charset
|   |
|   |── Charset                            - Encodes/decodes character sets
|   |── CharsetDecoder                     - Converts bytes to characters
|   └── CharsetEncoder                     - Converts characters to bytes
|
|── java.nio.channels
|   |
|   |── Channel (interface)                - Base interface for all channels
|   |   |
|   |   |── ReadableByteChannel            - Reads byte data
|   |   |── WritableByteChannel            - Writes byte data
|   |   |
|   |   └── ByteChannel                    - Supports both read and write
|   |       |
|   |       └── SeekableByteChannel        - Supports random access
|   |
|   |── FileChannel                        - File I/O with random access
|   |
|   |── AsynchronousFileChannel            - Asynchronous file operations
|   |
|   |── SelectableChannel (abstract class) - Supports non-blocking I/O
|   |   |
|   |   |── SocketChannel                  - TCP client channel
|   |   |── ServerSocketChannel            - TCP server channel
|   |   |── DatagramChannel                - UDP datagram channel
|   |   |── Pipe.SourceChannel             - Read end of a Pipe
|   |   └── Pipe.SinkChannel               - Write end of a Pipe
|   |
|   |── Pipe                               - Communication between threads
|   |
|   |── Selector                           - Monitors multiple channels
|   |
|   |── SelectionKey                       - Channel registration with Selector
|   |
|   |── Channels                           - Utility methods for channels
|   |
|   └── InterruptibleChannel               - Allows interruption of blocking I/O
|
|── java.nio.file
    |
    |── Path                               - Represents a file or directory path
    |── Files                              - Utility methods for file operations
    |── Paths                              - Factory for creating Path objects
    |
    |── WatchService                       - Monitors file system changes
    |   |
    |   |── WatchKey                       - Watch registration
    |   └── WatchEvent                  - File system change event
    |
    |── DirectoryStream                    - Iterates directory entries
    |
    |── FileSystem                         - Represents a file system
    |
    └── FileSystems                        - Access to available file systems

Buffer Classes


Class

One-liner Definition

Real-Time Use Case

Code Example

ByteBuffer        

Stores binary data for NIO operations.

Read/write file in chunks.

ByteBuffer buffer = ByteBuffer.allocate(1024);

CharBuffer

Stores character data for NIO.

Char conversion without IO.

CharBuffer cb = CharBuffer.allocate(100);


Channel Interfaces


Class

One-liner Definition

Real-Time Use Case

Code Example

FileChannel

Reads/writes files via ByteBuffer.

High-performance I/O.

FileChannel fc = FileChannel.open(path);

SocketChannel

Reads/writes data over TCP connection.

Build non-blocking client.

SocketChannel sc = SocketChannel.open();

ServerSocketChannel        

Listens for TCP connections.

Build scalable NIO server.

ServerSocketChannel ssc = ServerSocketChannel.open();


File System Classes


Class/Interface

One-liner Definition

Real-Time Use Case

Code Example

Path (interface)

Represents file path.

Replaces File in modern I/O.

Path path = Paths.get("data.txt");

Files (class)

Utilities to work with Path.

Copy, read, write, delete files.

Files.copy(p1, p2);

WatchService    

Watches file system for changes.

Auto-refresh on file changes.

watcher = FileSystems.getDefault().newWatchService();

Selector (class)                

Manages multiple channels (I/O multiplexing).

Build concurrent servers.

Selector selector = Selector.open();





When to use Classic vs NIO


Scenario

Classic I/O (java.io)

NIO (java.nio)

Simple file read/write

YES (Recommended)

YES

Object serialization

YES (ObjectInputStream/ObjectOutputStream)

NO

Random file access

YES (RandomAccessFile)

YES (FileChannel)

Memory-mapped files

NO

YES (FileChannel.map())

High-performance servers

NO

YES (Selector, SocketChannel)

Non-blocking I/O

NO

YES

File system watching

NO

YES (WatchService)

Reading all lines as Stream

NO

YES (Files.lines(path))

Large file processing

Limited

YES

Channel-based I/O

NO

YES

Buffer-based I/O

NO

YES (ByteBuffer)

Scalable network applications

NO

YES





Real-Time Java I/O Examples and Use Cases


1. File – Check if a file exists and create it

File file = new File("data.txt");

if (!file.exists()) {

    file.createNewFile();

}


2. BufferedReader – Read file line-by-line

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {

    String line;

    while ((line = reader.readLine()) != null) {

        System.out.println(line);

    }

}


3. ObjectOutputStream – Serialize an object

try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.ser"))) {

    out.writeObject(new Employee("Sameer", 123));

}


4. RandomAccessFile – Modify file content at position

RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");

raf.seek(5);  // Move to byte 5

raf.writeUTF("inserted");

raf.close();





Real-Time Java NIO Examples and Use Cases


a) Read and write using Path & Files

Path path = Paths.get("sample.txt");

Files.writeString(path, "Hello NIO");

String content = Files.readString(path);

System.out.println(content);


b) List files in directory

try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get("docs"))) {

    for (Path entry : stream) {

        System.out.println(entry.getFileName());

    }

}


c) FileChannel – Random access read/write

try (RandomAccessFile raf = new RandomAccessFile("file.txt", "rw")) {

    FileChannel channel = raf.getChannel();

    ByteBuffer buffer = ByteBuffer.allocate(48);

    channel.read(buffer);

    buffer.flip();

    while (buffer.hasRemaining()) {

        System.out.print((char) buffer.get());

    }

}


d) WatchService – Monitor file changes

WatchService watchService = FileSystems.getDefault().newWatchService();

Path dir = Paths.get("watched");

dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);


WatchKey key = watchService.take();

for (WatchEvent<?> event : key.pollEvents()) {

    System.out.println("File created: " + event.context());

}


e) Non-blocking ServerSocketChannel

ServerSocketChannel serverChannel = ServerSocketChannel.open();

serverChannel.bind(new InetSocketAddress(5000));

serverChannel.configureBlocking(false);

Selector selector = Selector.open();

serverChannel.register(selector, SelectionKey.OP_ACCEPT);





Best Practices


Area

Best Practice

Resource Handling

Always close streams using try-with-resources

Performance

Use buffered streams for large I/O

Character Encoding

Specify encoding explicitly: new InputStreamReader(in, UTF_8)

Error Handling

Catch and log exceptions meaningfully

Use NIO for modern needs

Use Files, Path, WatchService instead of legacy File where possible

Avoid File Locking Issues

Use FileChannel with lock() method for concurrent writes

Prefer Streams

Use Files.lines(path).filter(...) for functional-style processing





Summary Comparison – Classic vs NIO


Feature

Legacy I/O (java.io)

NIO (java.nio)

API Style

Stream-based

Buffer + Channel

Blocking

Always

Can be non-blocking

Performance

Less efficient

Faster with large data

File Operations

Manual

Files.* utilities

Monitoring File System

Not supported

Supported via WatchService

Memory Mapping

Not supported

Supported via MappedByteBuffer







Stream vs Buffer vs Channel Comparison

Feature

Stream (java.io)

Buffer (java.nio)

Channel (java.nio.channels)

Used In 

Classic I/O (Blocking)

NIO (Non-blocking, buffer-based)

NIO (Non-blocking, selectable)

Type

Abstraction over data flow

Container for data

Connection to I/O entities (file, socket)

Direction

One-way (InputStream/OutputStream)

N/A (data holder)

Typically bi-directional

How it Works

Data is read/written byte-by-byte or char-by-char

Data is first stored in buffer, then read/written in bulk

Works with Buffer to transfer data efficiently

Efficiency

Less efficient for large I/O

Helps improve performance

High performance with selectors

Blocking?

Blocking I/O

Non-blocking data storage

Supports non-blocking I/O

Control Over Data

Little – reads/writes sequentially

More – has position, limit, capacity

High – supports selectors, multiplexing

Examples

InputStream, Reader, FileOutputStream

ByteBuffer, CharBuffer

FileChannel, SocketChannel, DatagramChannel

  • Stream: A flow of data (bytes or characters) from a source to a destination, used in traditional (blocking) I/O.
  • Buffer: A memory block that temporarily stores data during transfer between entities, used in Java NIO.
  • Channel: A bi-directional communication path (like a pipe) for reading/writing data, used with NIO and operates with buffers.
Real-World Analogy
  • StreamLike a straw – you suck data in a fixed stream
  • Buffer
  • Like a glass – you fill it once and drink multiple times
  • Channel
  • Like a pipe – allows two-way flow, can open/close, multiplex


Java I/O Comparison: Best Use Cases

Component

Best Use Cases

InputStream / OutputStream

Reading/writing binary data such as images, audio, video, PDFs, ZIPs from files, sockets, or network sources.

Reader / Writer

Reading/writing character/text data from files, network, or console (supports Unicode characters).

BufferedInputStream / BufferedReader

Reading large data chunks efficiently. Reduces system calls by buffering input. Great for large file reads or line-by-line text reading.

BufferedOutputStream / BufferedWriter

Efficient writing to disk or socket. Use when writing large text or binary data.

DataInputStream / DataOutputStream

Reading and writing Java primitive types (int, long, double, etc.) in a portable binary format.

ObjectInputStream / ObjectOutputStream

Serialization: Saving and restoring objects across files, network or for caching. (e.g., saving game state or object graph).

ByteArrayInputStream / ByteArrayOutputStream

Temporary in-memory buffer for binary data. Often used in compression, encryption, or when mocking streams in testing.

CharArrayReader / CharArrayWriter

Same as above but for character data. Useful in templating, XML/HTML generation.

FileReader / FileWriter

Simple file reading/writing of text files with character data. Suitable for small to medium-sized files.

FileInputStream / FileOutputStream

Reading/writing raw binary file data, especially for media files. Also used as base for wrapping with buffering or decoding.

PipedInputStream / PipedOutputStream

Inter-thread communication. Implementing producer-consumer pattern between two threads via a stream.

Scanner

Parsing structured text from input sources (e.g., parsing user input, log files, CSVs). Good for interactive CLI apps.

PrintWriter / PrintStream

Writing formatted text easily (e.g., println, printf). Best for console output, log files, or writing formatted reports.

Channel

Non-blocking, faster, and parallel access to data. Best for file I/O, network servers, and concurrent processing using Selector.

Buffer (ByteBuffer, CharBuffer, etc.)

Holding temporary data in memory. Core part of NIO for efficient read/write between Channels.

Selector

Managing multiple channels with a single thread. Ideal for scalable network servers (e.g., chat apps, HTTP servers).

Memory-Mapped Files

Fast random access to large files (e.g., logs, DB files). Memory-efficient. Useful in Big Data or file-based caching.

InputStreamReader / OutputStreamWriter

Bridging between bytes and characters (e.g., reading text from a byte stream using specific encoding like UTF-8).

SequenceInputStream

Combine multiple streams into one. Useful for joining multiple file parts or streams together.

PushbackInputStream / PushbackReader

For lookahead or rollback while parsing input (e.g., tokenizers, compilers, interpreters).



Tips:
  • Classic IO (Stream/Reader): Simple, blocking, best for file-based or small-scale I/O.
  • NIO (Channel/Buffer/Selector): Non-blocking, scalable, best for high-performance apps.
  • Buffered Variants: Always wrap unbuffered streams for performance unless memory is a constraint.
  • Object/Data Streams: Use when object or primitive preservation is needed across I/O.