In Java, an I/O Stream is an abstraction representing a continuous sequence of data flowing from a source (Input Stream) to a destination sink (Output Stream). Streams isolate applications from physical hardware differences (hard disks, network sockets, keyboard inputs):
InputStream and OutputStream. Process raw 8-bit binary bytes. Ideal for binary files such as images (PNG, JPEG), compiled bytecode (.class), audio, and serialized objects.
Reader and Writer. Process 16-bit Unicode characters. Automatically handle character encoding translations (UTF-8, ASCII), making them ideal for plain-text documents and CSV files.
| Stream Category | Fundamental Unit | Abstract Root Classes | Primary Implementations | Best Suited For |
|---|---|---|---|---|
| Byte Streams | 8-bit Bytes | InputStream / OutputStream |
FileInputStream, FileOutputStream |
Binary files (Images, Audio, PDF, Object streams) |
| Character Streams | 16-bit Characters | Reader / Writer |
FileReader, FileWriter |
Text files (JSON, CSV, Log files, Source code) |
java.io.File Class & File Operations
The File class does not read or write file contents directly; it represents an abstract pathname pointing to a file or directory on the host operating system:
file.exists(): Returns true if the physical file or directory exists on disk.file.createNewFile(): Atomically creates a new empty file. Returns false if already present.file.mkdir() / file.mkdirs(): Creates directories (mkdirs() creates missing parent directories).file.length(): Returns file size in bytes.file.delete(): Deletes the file or empty directory from the filesystem.
Classes like FileReader and FileWriter execute a physical disk read/write for every single character or byte, causing massive OS syscall overhead.
BufferedReader and BufferedWriter wrap around low-level streams by maintaining an internal 8KB memory buffer:
BufferedReader.readLine(): Reads an entire line of text until a newline delimiter is reached, returning a String.BufferedWriter.newLine(): Emits a platform-independent newline separator.true as the second argument to the constructor: new FileWriter(file, true).readLine() in nanosecondsScanner Class & Try-with-Resources (ARM)
Prior to Java 7, closing file streams required complex boilerplate in a finally block, which itself could throw IOException.
Java 7 introduced Try-with-Resources: any resource implementing java.lang.AutoCloseable declared inside the try (...) header is automatically closed when execution exits the try block, whether normally or via an unhandled exception:
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { ... }
Scanner Utility Class
Located in java.util, Scanner parses primitive types and strings using regular expression delimiters:
Scanner sc = new Scanner(new File("scores.csv"));
Offers convenience methods like hasNextInt(), nextInt(), hasNextLine(), and nextLine().
transient Keyword
Serialization is the process of converting the in-memory state of an object graph into a sequential stream of binary bytes to save it to disk or transmit it across network sockets.
Deserialization is the reverse process: reconstructing the live object on the JVM Heap from the byte stream.
Serializable Interface: A class must implement this marker interface (contains no methods) to authorize the JVM to serialize its instances.ObjectOutputStream.writeObject(obj): Serializes an object to a stream.ObjectInputStream.readObject(): Deserializes a stream back into an object reference.serialVersionUID: A unique 64-bit identifier used during deserialization to verify that the sender and receiver of an object have loaded classes for that object that are compatible.transient Keyword: Marks sensitive fields (e.g., passwords, credit card numbers, encryption keys) that must NOT be serialized. When deserialized, transient fields receive default values (null or 0).UserSession (user, pass)
ObjectOutputStream ➔ObjectInputStream ➔UserSession (user, null)
This complete application demonstrates: writing and appending log records using BufferedWriter, reading line-by-line via BufferedReader in a try-with-resources block, and serializing/deserializing a secure AuditRecord object with a transient security hash.
import java.io.*;
// 1. Serializable Entity with Transient Security Field
class AuditSnapshot implements Serializable {
private static final long serialVersionUID = 1L;
private final String operatorId;
private final int recordsProcessed;
private transient final String sessionToken; // TRANSIENT: Never written to disk!
public AuditSnapshot(String operatorId, int recordsProcessed, String sessionToken) {
this.operatorId = operatorId;
this.recordsProcessed = recordsProcessed;
this.sessionToken = sessionToken;
}
@Override
public String toString() {
return String.format("AuditSnapshot[Operator: %s, Records: %d, Token: %s]",
operatorId, recordsProcessed, (sessionToken == null ? "<STRIPPED_TRANSIENT>" : sessionToken));
}
}
// 2. Execution Controller
public class EnterpriseLogManager {
private static final String TEXT_LOG = "audit_trail.log";
private static final String SER_FILE = "snapshot.ser";
public static void main(String[] args) {
System.out.println("==================================================");
System.out.println(" DEVASCENT FILE HANDLING & SERIALIZATION ENGINE ");
System.out.println("==================================================");
// Step 1: Write and Append Text using Try-with-resources
try (BufferedWriter writer = new BufferedWriter(new FileWriter(TEXT_LOG, true))) {
writer.write("2026-10-15 19:40:01 | AUTH_LOGIN | User: admin_shivan | SUCCESS");
writer.newLine();
writer.write("2026-10-15 19:40:05 | PAYMENT_TXN | Ref: TXN-8941 | DISPATCHED");
writer.newLine();
System.out.println("✓ Successfully appended entries to " + TEXT_LOG);
} catch (IOException e) {
System.err.println("Write error: " + e.getMessage());
}
// Step 2: Read Back File Line-by-Line
System.out.println("--- Reading Log Stream via BufferedReader ---");
try (BufferedReader reader = new BufferedReader(new FileReader(TEXT_LOG))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(" > " + line);
}
} catch (IOException e) {
System.err.println("Read error: " + e.getMessage());
}
// Step 3: Object Serialization with Transient Security Field
System.out.println("--------------------------------------------------");
AuditSnapshot originalSnapshot = new AuditSnapshot("sec_officer_01", 240, "SECRET_SESSION_TOKEN_XYZ");
System.out.println("Pre-Serialization Object : " + originalSnapshot);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(SER_FILE))) {
oos.writeObject(originalSnapshot);
System.out.println("✓ Serialized object saved to " + SER_FILE);
} catch (IOException e) {
System.err.println("Serialization error: " + e.getMessage());
}
// Step 4: Object Deserialization
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SER_FILE))) {
AuditSnapshot restored = (AuditSnapshot) ois.readObject();
System.out.println("Deserialized Live Object : " + restored);
} catch (IOException | ClassNotFoundException e) {
System.err.println("Deserialization error: " + e.getMessage());
} finally {
// Clean up scratch files
new File(TEXT_LOG).delete();
new File(SER_FILE).delete();
}
System.out.println("==================================================");
}
}
InputStream, OutputStream) process raw 8-bit binary data without character encoding interpretations, making them suitable for binary files like images, audio, and compiled bytecode. Character Streams (Reader, Writer) process 16-bit Unicode characters and automatically handle character set encoding conversions (e.g., UTF-8, ASCII), making them ideal for textual files like JSON, XML, and source code.
BufferedReader significantly faster than FileReader?FileReader initiates an expensive operating system kernel system call (syscall) for each individual character read from disk. BufferedReader maintains an internal 8KB memory buffer, fetching thousands of characters in a single disk read operation. Subsequent read calls simply access this in-memory buffer without disk interaction, reducing I/O syscall overhead by up to 90%.
close() method on all declared resources when exiting the try block. To be eligible for Try-with-Resources, a class must implement the java.lang.AutoCloseable (or java.io.Closeable) interface. If both the try block and close() throw exceptions, the close exception is preserved as a suppressed exception (accessible via e.getSuppressed()).
serialVersionUID in Java Serialization?serialVersionUID is a unique 64-bit hash used during deserialization to verify that the loaded class matches the exact version of the serialized object. If the class structure is modified without declaring an explicit serialVersionUID, the compiler calculates a new hash, causing deserialization to fail with an InvalidClassException. Declaring an explicit private static final long serialVersionUID ensures backward compatibility across software versions.
transient keyword?transient modifier is applied to instance variables to prevent them from being serialized. When an object is written via ObjectOutputStream, transient fields are skipped and omitted from the binary byte stream. Upon deserialization, transient fields are initialized to their default values (null for objects, 0 for numeric primitives, false for booleans). It is primarily used for security credentials and temporary calculated caches.
true as the second parameter to the FileWriter or FileOutputStream constructor:
BufferedWriter writer = new BufferedWriter(new FileWriter("app.log", true));
false, the file is truncated and overwritten from the beginning.
Scanner and BufferedReader?BufferedReader is a lightweight, synchronized stream that reads raw character lines with high speed and an 8KB buffer, returning plain Strings. Scanner is an unsynchronized parser with a smaller (1KB) buffer that parses tokens using regular expressions, offering type-conversion utility methods (nextInt(), nextDouble()). BufferedReader is much faster for large files, while Scanner is convenient for parsed token input.
Serializable?Serializable but its superclass does not, the subclass can still be serialized. However, during deserialization, the JVM invokes the superclass's no-argument constructor to initialize the inherited fields. If the non-serializable superclass does not have an accessible no-arg constructor, deserialization will fail with an InvalidClassException.