Study guide

11.1 Classic Java I/O Streams and Readers/Writers

Oracle Java SE 21 Developer 1Z0-830

Track your progress as you read

Finished sections get a check mark.

1 Chapter 1: Java Basics, Primitive Types, and Operators

1.1 Program Structure, Primitives, Literals, Scopes, and var 1.2 Operators, Precedence, and Type Casting 1.3 Strings, Text Blocks, and StringBuilder 1.4 Date and Time APIs (java.time)

2 Chapter 2: Controlling Program Flow & Pattern Matching

2.1 Conditional Logic: if-else and Ternary Operators 2.2 Switch Statements and Switch Expressions 2.3 Pattern Matching for switch and instanceof 2.4 Loops and Branching Statements

3 Chapter 3: Object-Oriented Design & Class Architecture

3.1 Class Declarations, Constructors, and Object Lifecycle 3.2 Encapsulation and Access Modifiers 3.3 Inheritance, Method Overriding, and Polymorphism 3.4 Abstract Classes and Interfaces 3.5 Nested, Inner, Local, and Anonymous Classes 3.6 Method Overloading, Overload Resolution, and Varargs

4 Chapter 4: Modern Java Data Modeling: Records, Enums, and Sealed Types

4.1 Java Records, Canonical/Compact Constructors, and Component Accessors 4.2 Java Enums: Fields, Methods, and Abstract Constant-Specific Behavior 4.3 Sealed Classes and Interfaces: permits, final, non-sealed, and Hierarchy Architecture 4.4 Advanced Record Patterns (JEP 440), Nested Deconstruction, and Sealed Hierarchy Exhaustiveness

5 Chapter 5: Exception Handling and Annotations

5.1 Exception Hierarchy: Checked vs. Unchecked Exceptions 5.2 Exception Handling: try, catch, finally, and Multi-Catch 5.3 Resource Management: try-with-resources and AutoCloseable 5.4 Custom Exceptions and Best Practices 5.5 Java Annotations: Built-in, Meta-Annotations, and Custom Types

6 Chapter 6: Generics and the Collections Framework

6.1 Generics Fundamentals and Generic Methods 6.2 Wildcards, Type Bounds, and Type Erasure 6.3 Core Collections: Lists, Sets, Queues, and Maps 6.4 Sequenced Collections in Java 21 6.5 Sorting, Comparable, and Comparator 6.6 Arrays: Creation, Multidimensional Forms, and the Arrays Utility Class

7 Chapter 7: Functional Programming & Lambda Expressions

7.1 Built-in Functional Interfaces in java.util.function 7.2 Lambda Expressions, Variable Scopes, and Effectively Final 7.3 Method References: Four Varieties and Edge Cases 7.4 Working with java.util.Optional

8 Chapter 8: The Stream API and Data Pipelines

8.1 Stream Creation and Intermediate Operations 8.2 Terminal Operations and Reductions 8.3 Advanced Collectors, Grouping, Partitioning, and Teeing 8.4 Primitive Streams and Parallel Stream Processing

9 Chapter 9: Concurrency, Multithreading, and Virtual Threads

9.1 Thread Fundamentals, Creation, and Lifecycle 9.2 Thread Safety, Synchronization, and the Java Memory Model 9.3 Concurrency Utilities: ExecutorService, Callable, and Future 9.4 Concurrent Collections, Atomics, and Explicit Locks 9.5 Virtual Threads in Java 21 (Carrier Threads, Pinning, and Best Practices)

10 Chapter 10: The Java Platform Module System (JPMS)

10.1 Module Architecture and module-info.java Declarations 10.2 Module Directives: requires, exports, opens, provides, and uses 10.3 Modular Compilation, Packaging, and CLI Execution 10.4 Module Types, Migration Strategies, ServiceLoader SPI, and JLink

11 Chapter 11: Java I/O, NIO.2, and Object Serialization

11.1 Classic Java I/O Streams and Readers/Writers 11.2 Java NIO.2 File API: Path, Paths, and File System Operations 11.3 Advanced NIO.2 Stream Operations and Directory Traversal 11.4 Java Object Serialization and Record Serialization

12 Chapter 12: Implementing Localization and Internationalization

12.1 Locales and Resource Bundles 12.2 Formatting and Parsing Numbers, Currencies, Dates, Times, and Messages

11.1 Classic Java I/O Streams and Readers/Writers

Key Takeaways Explain This Topic AI

Java Classic I/O cleanly bifurcates into 8-bit byte streams (InputStream and OutputStream) for binary data and 16-bit character streams (Reader and Writer) for Unicode text.

Low-level node streams interact directly with data sources or sinks, whereas processing wrapper streams decorate existing streams to add buffering, data formatting, and bridging capabilities.

Starting in Java 18 (JEP 400), UTF-8 is the standard default charset across all standard Java APIs, though explicit StandardCharsets.UTF_8 passing remains best practice for deterministic cross-platform behavior.

BufferedReader.readLine() strips line terminators and returns null at EOF; PrintWriter and PrintStream never throw checked IOException and track errors via checkError().

System.console() returns null in non-interactive environments such as IDEs or background processes, and its readPassword() method returns a mutable char array to facilitate immediate memory zeroing for security.

Last updated: September 2026

Classic Java I/O: Streams, Readers, Writers, and Buffered I/O

Input/Output (I/O) in Java provides the fundamental architecture for reading from and writing to external data sources, including physical disk files, network sockets, in-memory buffers, and system consoles. The original java.io package organizes all data movement around sequential streams.

For the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam, developers must master the separation between 8-bit byte streams and 16-bit character streams, the Decorator design pattern used in stream chaining, modern transfer methods such as transferTo() , character encoding standards under JEP 400 , stream mark/reset capabilities, and interactive terminal handling using java.io.Console .

1. The Classic I/O Architecture: Byte vs. Character Streams

The java.io package cleanly bifurcates all stream operations based on data granularity:

Byte Streams ( InputStream and OutputStream ) : Transfer raw binary data as 8-bit bytes (values ranging from 0 to 255 , or signed -128 to 127 ). Byte streams are used for binary payloads, images, audio, compiled .class files, compressed ZIP archives, and serialized objects.

Character Streams ( Reader and Writer ) : Transfer 16-bit Unicode characters ( char ). Character streams automatically handle character encoding and decoding, translating raw byte sequences to and from human-readable characters according to a specified Charset .

  • Feature Byte Streams ( java.io ) Character Streams ( java.io )
  • Root Abstract Classes InputStream , OutputStream Reader , Writer
  • Unit of Transfer 8-bit bytes ( byte / int ) 16-bit Unicode characters ( char / int )

Primary Use Cases Binary files, media, compiled classes, network sockets Plain text, CSV, JSON, XML, configuration files

  • Encoding Awareness Unaware (transfers raw uninterpreted bytes) Encoding-aware (uses Charset , default UTF-8)
  • Direct File Node Streams FileInputStream , FileOutputStream FileReader , FileWriter
  • Buffering Wrappers BufferedInputStream , BufferedOutputStream BufferedReader , BufferedWriter
  • Formatted Output PrintStream (e.g., System.out ) PrintWriter
  • Bridge Streams InputStreamReader (byte to char), OutputStreamWriter (char to byte) N/A
  • Node Streams vs. Processing (Wrapper) Streams

Java Classic I/O heavily relies on the Decorator Design Pattern . Stream classes are divided into two operational categories:

Node Streams (Low-Level Streams) : Connect directly to a physical data source or destination (such as a disk file, memory array, or network socket). Examples include FileInputStream , FileOutputStream , FileReader , FileWriter , ByteArrayInputStream , ByteArrayOutputStream , CharArrayReader , and CharArrayWriter .

Processing Streams (High-Level / Wrapper Streams) : Wrap around an existing stream to add functionality such as buffering, primitive data parsing, character encoding translation, or object serialization. Examples include BufferedInputStream , BufferedOutputStream , BufferedReader , BufferedWriter , InputStreamReader , OutputStreamWriter , DataInputStream , DataOutputStream , and ObjectInputStream . // Chaining a low-level file node stream with a character bridge and a buffering decorator:

  • try (BufferedReader reader = new BufferedReader(
  • new InputStreamReader(
  • new FileInputStream("application.log"),
  • StandardCharsets.UTF_8))) {
  • String line;
  • while ((line = reader.readLine()) != null) {
  • System.out.println("Log entry: " + line);
  • }
  • }

Important

Closing Chained Streams : When wrapper streams are chained together in a Decorator pipeline, invoking close() on the outermost wrapper stream automatically flushes and closes all underlying wrapped streams. You do not need to call close() on each individual inner stream. When using try-with-resources , declare only the outermost wrapper or declare each in order.

2. Byte Streams: InputStream and OutputStream

InputStream and OutputStream are the abstract roots of the byte stream hierarchy.

Reading Bytes from an InputStream

The fundamental read() methods in InputStream return an int rather than a byte :

public abstract int read() throws IOException : Reads the next single byte of data. Returns an int in the range 0 to 255 . If the end of the stream (EOF) is reached, it returns -1 .

public int read(byte[] b) throws IOException : Reads up to b.length bytes into the specified byte array. Returns the actual count of bytes read as an int , or -1 if EOF is reached before reading any bytes.

public int read(byte[] b, int off, int len) throws IOException : Reads up to len bytes into array b starting at index offset off . Returns the number of bytes read, or -1 if EOF is reached. try (InputStream in = new FileInputStream("source.bin")) {

  • int byteRead;
  • // Sentinel loop pattern: read() returns -1 at end-of-stream
  • while ((byteRead = in.read()) != -1) {
  • byte b = (byte) byteRead; // Cast int [0..255] back to signed byte

// Process individual byte...

  • }
  • }
  • Why read() Returns int Instead of byte

In Java, the primitive byte type is signed, with a numerical range of -128 to +127 . A byte value of 0xFF represents -1 in two's complement arithmetic. If read() returned a byte , the valid data byte 0xFF ( -1 ) would be completely indistinguishable from the end-of-stream sentinel signal ( -1 ). Returning an int allows valid byte values to be represented as positive unsigned integers from 0 to 255 , while reserving the integer -1 exclusively for EOF.

Modern Java Byte Stream Methods (Java 9 to Java 21)

Modern Java releases introduced high-performance methods directly onto InputStream and OutputStream : // 1. in.transferTo(OutputStream out) - Java 9+

// Reads all remaining bytes from this input stream and writes them directly to the target output stream.

// Returns the total number of bytes transferred as a 64-bit long. Does not close either stream.

  • try (InputStream in = new FileInputStream("source.bin");
  • OutputStream out = new FileOutputStream("backup.bin")) {
  • long bytesTransferred = in.transferTo(out);
  • System.out.println("Transferred: " + bytesTransferred + " bytes");
  • }

// 2. in.readAllBytes() - Java 9+

// Reads all remaining bytes into a newly allocated byte array on the heap.

  • try (InputStream in = new FileInputStream("payload.dat")) {
  • byte[] allData = in.readAllBytes();
  • }

// 3. in.readNBytes(int len) - Java 11+

// Reads up to len bytes into a newly allocated byte array.

  • try (InputStream in = new FileInputStream("header.dat")) {
  • byte[] header = in.readNBytes(128); // Returns array of length <= 128
  • }

// 4. in.readNBytes(byte[] b, int off, int len) - Java 9+

// Reads exactly len bytes into array b starting at offset off, blocking until len bytes are read or EOF is reached.

// Returns the actual count of bytes read into the buffer.

Writing Bytes to an OutputStream

public abstract void write(int b) throws IOException : Writes the specified byte (the 8 low-order bits of the int argument; the 24 high-order bits are discarded).

public void write(byte[] b) throws IOException : Writes all b.length bytes from the byte array.

public void write(byte[] b, int off, int len) throws IOException : Writes len bytes from array b starting at offset off .

public void flush() throws IOException : Flushes this output stream and forces any buffered output bytes to be written out to the underlying operating system device or socket buffer.

Buffered Byte Streams: BufferedInputStream and BufferedOutputStream

Direct file operations through FileInputStream or FileOutputStream invoke an underlying operating system system call for every byte read or written, creating substantial kernel transition overhead. Wrapping node streams with BufferedInputStream and BufferedOutputStream maintains an internal memory buffer (default size: 8192 bytes / 8 KB ): try (InputStream in = new BufferedInputStream(new FileInputStream("large.iso"));

  • OutputStream out = new BufferedOutputStream(new FileOutputStream("copy.iso"))) {
  • in.transferTo(out);
  • out.flush(); // Flushes remaining buffered bytes to disk
  • }
  • Specialized Byte Streams: DataInputStream and DataOutputStream

DataInputStream and DataOutputStream implement DataInput and DataOutput to read and write Java primitive data types and portable strings in machine-independent binary format: try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("records.dat"))) {

  • dos.writeInt(101);
  • dos.writeDouble(99.95);
  • dos.writeBoolean(true);
  • dos.writeUTF("Alice Smith"); // Modified UTF-8 format
  • }
  • try (DataInputStream dis = new DataInputStream(new FileInputStream("records.dat"))) {
  • int id = dis.readInt();
  • double price = dis.readDouble();
  • boolean active = dis.readBoolean();
  • String name = dis.readUTF();

// Fields must be read in the exact order and types they were written!

  • }
  • 3. Character Streams: Reader , Writer , and Charset Standards

Character streams process sequences of 16-bit Unicode characters.

Standard Charset and JEP 400

Prior to Java 18, the default character encoding depended on the host operating system, regional locale, and user configuration (e.g., Windows-1252 on Windows, UTF-8 on Linux and macOS). This led to severe data corruption bugs when text files generated on one operating system were read on another.

Starting with Java 18 (JEP 400) , the standard default charset for all standard Java APIs is unconditionally UTF-8 across all operating systems and runtimes. // In Java 21, FileReader and FileWriter use StandardCharsets.UTF_8 by default

  • try (FileWriter writer = new FileWriter("notes.txt", StandardCharsets.UTF_8, true)) {
  • // The boolean parameter 'true' enables append mode (writing to end of file)

writer.write("Appending entry to journal.

  • ");
  • }
  • BufferedReader and BufferedWriter

BufferedReader and BufferedWriter are essential for efficient character stream processing:

BufferedReader.readLine() : Reads a line of text. A line is considered terminated by line feed ( ), carriage return ( ), or carriage return followed immediately by line feed ( ). Returns the String content without any terminating characters, or null when the end of the stream is reached.

BufferedReader.lines() : Returns a lazy Stream<String> containing the lines read from this BufferedReader .

BufferedWriter.newLine() : Writes a platform-specific line separator string (e.g., on Windows, on Unix) to the stream. try (BufferedReader br = new BufferedReader(new FileReader("data.csv", StandardCharsets.UTF_8))) {

  • // Functional line processing via Stream API
  • br.lines()
  • .filter(line -> !line.startsWith("#")) // Skip comment lines
  • .map(String::trim)
  • .forEach(System.out::println);
  • }
  • PrintWriter and PrintStream

PrintStream (byte stream wrapper, used by System.out and System.err ) and PrintWriter (character stream wrapper) provide high-level formatted output methods ( print , println , printf , format ).

Key characteristics tested on the 1Z0-830 exam:

No Checked Exceptions : Methods in PrintWriter and PrintStream never throw checked IOException . Instead, any internal I/O errors set an internal boolean error flag that can be checked programmatically using the checkError() method.

Auto-Flush Configuration : Both classes feature constructor overloads with an autoFlush boolean parameter (e.g., new PrintWriter(writer, true) ). When autoFlush is true , invoking println() , printf() , or format() automatically flushes the buffer. try (PrintWriter pw = new PrintWriter(new FileWriter("report.txt"), true)) {

  • pw.printf("Student: %s | Score: %.2f%n", "Eleanor", 94.5);
  • if (pw.checkError()) {
  • System.err.println("An I/O error occurred during writing!");
  • }
  • }
  • 4. Stream Navigation: mark() , reset() , and skip()

The InputStream and Reader classes provide navigation primitives to skip forward or backtrack:

boolean markSupported() : Returns true if this stream instance supports the mark() and reset() methods.

void mark(int readAheadLimit) : Marks the current position in this input stream. The readAheadLimit argument specifies the maximum number of bytes/chars that can be read before the marked position becomes invalid.

void reset() throws IOException : Repositions this stream to the position at the time the mark() method was last called. If markSupported() returns false or the mark has been invalidated by reading beyond readAheadLimit , reset() throws an IOException .

long skip(long n) throws IOException : Skips over and discards up to n bytes or characters from the stream, returning the actual number of bytes/characters skipped. InputStream is = new BufferedInputStream(new FileInputStream("data.bin"));

  • if (is.markSupported()) { // BufferedInputStream supports mark/reset (FileInputStream does NOT!)
  • is.mark(100); // Read up to 100 bytes ahead while retaining mark
  • int b1 = is.read();
  • int b2 = is.read();

is.reset(); // Returns stream read position back to the mark!

  • int b1Again = is.read(); // Reads b1 again
  • }

Warning

Direct node streams like FileInputStream and FileReader do NOT support mark() and reset() ( markSupported() returns false ). Invoking reset() on them immediately throws IOException . You must wrap them in a BufferedInputStream or BufferedReader to enable mark/reset capabilities.

5. Interactive Terminal Operations with java.io.Console

Java provides java.io.Console as a singleton interface to interact with the physical operating system terminal attached to the current JVM process. Console console = System.console();

if (console == null) {

System.out.println("No interactive console attached (running inside IDE, background daemon, or redirected I/O).");

  • return;
  • }
  • String username = console.readLine("Enter username: ");
  • char[] password = console.readPassword("Enter password for %s: ", username);
  • try {

// Process login credentials...

  • System.out.println("Authenticating user: " + username);
  • } finally {

// Security Best Practice: Zero out password memory immediately!

  • java.util.Arrays.fill(password, ' ');
  • }
  • Critical Exam Rules for java.io.Console

System.console() Null Check :

If the application is executed inside an IDE (IntelliJ IDEA, Eclipse, VS Code), run as a background service/daemon, or if standard I/O is redirected via shell pipes ( java App < input.txt > output.txt ), System.console() returns null .

Invoking methods on console without performing a null check will throw NullPointerException .

readPassword() Security Guarantee :

console.readPassword() disables terminal character echoing and returns a char[] (character array), not a java.lang.String .

In Java, String objects are immutable and interned or retained in the JVM heap until garbage collected and memory overwritten. A char[] is mutable and can be immediately scrubbed (e.g., Arrays.fill(password, ' ') ) as soon as authentication finishes, preventing sensitive credentials from leaking in heap memory dumps.

Format String Support :

Both readLine(String fmt, Object... args) and readPassword(String fmt, Object... args) accept optional printf -style format strings to prompt the user.

Accessing Underlying Reader and Writer :

console.reader() returns a Reader .

console.writer() returns a PrintWriter .

6. Summary Comparison Table

Class Type Node / Wrapper Key Methods / Features

FileInputStream Byte Node Direct file byte reading; no buffering; no mark/reset support.

FileOutputStream Byte Node Direct file byte writing; append mode boolean flag.

BufferedInputStream Byte Wrapper 8 KB internal buffer; supports mark() and reset() .

BufferedOutputStream Byte Wrapper 8 KB internal buffer; reduces OS write calls; requires flush() .

InputStreamReader Bridge Wrapper Decodes raw bytes to characters using specified Charset .

OutputStreamWriter Bridge Wrapper Encodes characters to raw bytes using specified Charset .

FileReader Char Node Reads text files; defaults to UTF-8 in Java 18+ (JEP 400).

FileWriter Char Node Writes text files; append mode constructor; defaults to UTF-8.

BufferedReader Char Wrapper Buffers text; provides readLine() and lines() .

BufferedWriter Char Wrapper Buffers text; provides newLine() .

PrintStream Byte Wrapper println() , printf() , checkError() ; never throws IOException .

PrintWriter Char Wrapper println() , printf() , checkError() ; never throws IOException .

Console Terminal Singleton readLine() , readPassword() ( char[] ); returns null in IDEs/redirects.

Loading diagram... Java Classic I/O Hierarchy and Decorator Chaining