Skip to content
Java io 6 min read

FileReader

FileReader is Java’s simplest way to read text from a file. It treats file contents as a stream of characters rather than raw bytes, making it a natural fit whenever you need to read plain-text data like .txt, .csv, or .log files.

Filereader reader class hierarchy

What Is FileReader?

FileReader lives in the java.io package and extends InputStreamReader, which itself extends Reader. This places it firmly in Java’s character stream hierarchy — as opposed to byte streams like FileInputStream.

Reader
  └── InputStreamReader
        └── FileReader

Under the hood, FileReader wraps a FileInputStream and decodes bytes into characters using the platform’s default charset (typically UTF-8 on modern systems). For most everyday text reading tasks this works fine, but when you need explicit encoding control, use InputStreamReader with a named charset instead.

Note: FileReader reads characters, not bytes. If you need to read binary data (images, audio, etc.), use FileInputStream.

Creating a FileReader

You can create a FileReader from either a file path string or a File object.

import java.io.FileReader;
import java.io.File;
import java.io.IOException;

public class CreateExample {
    public static void main(String[] args) throws IOException {
        // From a path string
        FileReader fr1 = new FileReader("notes.txt");

        // From a File object
        File file = new File("notes.txt");
        FileReader fr2 = new FileReader(file);

        // Always close when done
        fr1.close();
        fr2.close();
    }
}

Warning: If the file does not exist, FileReader throws a FileNotFoundException (a subclass of IOException). Always handle or declare this exception.

Reading Characters: The Three read() Methods

FileReader inherits three reading methods from Reader:

MethodReturnsDescription
read()int (0–65535 or -1)Reads a single character
read(char[] cbuf)intFills the entire char array; returns count or -1
read(char[] cbuf, int off, int len)intReads up to len chars into cbuf starting at off

A return value of -1 always signals the end of the stream.

Reading One Character at a Time

import java.io.FileReader;
import java.io.IOException;

public class ReadOneChar {
    public static void main(String[] args) throws IOException {
        try (FileReader fr = new FileReader("notes.txt")) {
            int ch;
            while ((ch = fr.read()) != -1) {
                System.out.print((char) ch);
            }
        }
    }
}

Output (if notes.txt contains Hello, Java!):

Hello, Java!

Tip: The try-with-resources block (Java 7+) automatically closes the FileReader even if an exception occurs — always prefer it over a manual close() call.

Reading into a Char Array (Bulk Read)

Reading one character at a time is simple but slow — each call may hit the disk. Reading a chunk at a time is much more efficient:

import java.io.FileReader;
import java.io.IOException;

public class ReadChunk {
    public static void main(String[] args) throws IOException {
        try (FileReader fr = new FileReader("notes.txt")) {
            char[] buffer = new char[512];
            int charsRead;
            while ((charsRead = fr.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, charsRead));
            }
        }
    }
}

Note: Always use new String(buffer, 0, charsRead) rather than new String(buffer) — the array may be larger than the actual data read on the last iteration.

Checking Readiness with ready()

The ready() method returns true if the stream is ready to be read without blocking. This is rarely critical for file I/O (files don’t block like sockets do), but it can be useful in non-blocking patterns:

import java.io.FileReader;
import java.io.IOException;

public class ReadyExample {
    public static void main(String[] args) throws IOException {
        try (FileReader fr = new FileReader("notes.txt")) {
            if (fr.ready()) {
                System.out.println("Stream is ready — starting read.");
                int ch;
                while ((ch = fr.read()) != -1) {
                    System.out.print((char) ch);
                }
            }
        }
    }
}

A Complete, Practical Example

Here is a realistic snippet that reads a small text file and prints each line by tracking newline characters:

import java.io.FileReader;
import java.io.IOException;

public class ReadLines {
    public static void main(String[] args) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (FileReader fr = new FileReader("notes.txt")) {
            int ch;
            while ((ch = fr.read()) != -1) {
                if ((char) ch == '\n') {
                    System.out.println(sb);
                    sb.setLength(0);   // reset the builder
                } else {
                    sb.append((char) ch);
                }
            }
            // Print any remaining content after the last newline
            if (sb.length() > 0) {
                System.out.println(sb);
            }
        }
    }
}

Tip: For real line-by-line reading, wrap FileReader in a BufferedReader and call readLine(). It handles \r\n, \n, and \r endings automatically and is far more efficient.

Wrapping FileReader with BufferedReader

On its own, FileReader reads one character per call and typically results in one system call per character. Wrapping it in a BufferedReader adds an internal buffer (default 8 KB) so that many characters are fetched in a single I/O operation:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedExample {
    public static void main(String[] args) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader("notes.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

This is the most common pattern you will see in production code, and it is significantly faster than calling fr.read() in a loop for anything beyond tiny files.

Specifying a Charset (Java 11+)

Before Java 11, FileReader always used the platform default charset and there was no way to change it. Java 11 added two new constructors that accept a Charset:

import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class EncodingExample {
    public static void main(String[] args) throws IOException {
        // Explicitly read as UTF-8, regardless of platform default
        try (FileReader fr = new FileReader("notes.txt", StandardCharsets.UTF_8)) {
            int ch;
            while ((ch = fr.read()) != -1) {
                System.out.print((char) ch);
            }
        }
    }
}

Tip: If your application might run on Windows (where the default charset can still be windows-1252 in some configurations), always specify StandardCharsets.UTF_8 explicitly to prevent garbled text.

FileReader vs Other Text-Reading Classes

ClassBuffered?Encoding ControlReads Lines?Best For
FileReaderNoJava 11+ onlyNoSimple character-by-character reads
BufferedReaderYes (wraps Reader)Inherited from wrapped ReaderYes (readLine())Efficient line-by-line file reading
InputStreamReaderNoYes (any charset)NoReading bytes with custom encoding
ScannerYesYesYes (nextLine())Parsing tokens from text

Under the Hood

When you construct a FileReader, the JVM creates a StreamDecoder that sits between the raw byte source and your character stream. Here is what happens on each read() call:

  1. The StreamDecoder requests bytes from the underlying FileInputStream (one or more, depending on the charset — UTF-8 uses 1–4 bytes per character).
  2. It decodes the byte sequence into a Unicode code point (a char value).
  3. That int value is returned to your code.

Because each call potentially hits the OS to read bytes (if no buffering is in place), unbuffered FileReader on a spinning disk can be 10–100× slower than the same code wrapped in a BufferedReader. The OS and JVM do have their own page caches and read-ahead mechanisms, but explicitly buffering is still the right practice.

Internally, FileReader delegates to InputStreamReader, which means it inherits the getEncoding() method — useful for debugging charset issues:

try (FileReader fr = new FileReader("notes.txt")) {
    System.out.println("Encoding: " + fr.getEncoding());
}

Output (on a typical Linux/macOS system):

Encoding: UTF8

Note: getEncoding() returns the historical IANA name (e.g., UTF8), not the canonical Java name (UTF-8). They refer to the same charset.

  • BufferedReader — wrap FileReader with BufferedReader for fast line-by-line reading
  • FileWriter — the character-based counterpart for writing text to files
  • InputStreamReader — full charset control when reading bytes as characters
  • Byte vs Character Streams — understand when to use FileReader vs FileInputStream
  • FileInputStream — byte-level file reading for binary data
  • Scanner — a higher-level alternative for parsing text files token by token
Last updated June 13, 2026
Was this helpful?