Back to Blog
Java

Java FileReader: How to Read Text Files

Learn how to use Java FileReader to read character data from files, control encoding with charset-aware constructors, close resources, and improve performance with BufferedReader.

JavaFileReaderFile I/OCharacter StreamsBufferedReaderException Handling
Illustration of Java FileReader reading characters from a text file with a BufferedReader for efficiency.

The Java FileReader class is the simplest way to read character data from a file in Java. It treats the file as a stream of characters and, with the default constructors, decodes bytes according to the JVM's default charset. This makes it convenient for small text files when you know the file encoding matches the runtime environment, but it also introduces a few pitfalls that can cause subtle bugs.

What FileReader Does and When to Use It

FileReader extends InputStreamReader and is specifically designed to read character files. It opens a FileInputStream internally and, when you use the single-argument constructors, converts bytes to characters using the default charset of the JVM. Because of this, FileReader is best suited for files that use the same encoding as the platform, such as ASCII or UTF-8 on many modern systems.

You should use FileReader when:

  • You are reading a text file whose encoding matches the JVM's default charset.
  • You need a minimal API without extra configuration.
  • You are working with small files where buffering is not critical.

For anything else, especially files with a non-default encoding or large files that need efficient reading, pass an explicit charset to FileReader (Java 11+) or use InputStreamReader, and wrap the stream in a BufferedReader where appropriate.

Creating a FileReader and Reading Characters

The FileReader class provides these constructors:

ConstructorDescription
FileReader(String fileName)Opens the named file for reading.
FileReader(File file)Opens the file represented by the File object.
FileReader(FileDescriptor fd)Opens a file descriptor for reading.
FileReader(String fileName, Charset charset)Opens the named file with the given charset (Java 11+).
FileReader(File file, Charset charset)Opens the file with the given charset (Java 11+).

The String and File constructors without a charset throw FileNotFoundException if the file does not exist or cannot be opened. The charset-aware constructors are Java 11+ and can throw IOException.

Here is a minimal example that reads a single character:

import java.io.FileReader; import java.io.IOException; public class ReadSingleChar { public static void main(String[] args) { try (FileReader reader = new FileReader("example.txt")) { int charCode = reader.read(); if (charCode != -1) { System.out.println("First character: " + (char) charCode); } } catch (IOException e) { e.printStackTrace(); } } }

The read() method returns the character as an int value in the range 0 to 65535, or -1 if the end of the stream has been reached. Because it returns an int, you must cast it to char when you want to use the character value.

Reading one character at a time is inefficient for larger files. A better approach is to read into a character array:

char[] buffer = new char[1024]; int numCharsRead; while ((numCharsRead = reader.read(buffer)) != -1) { // process the characters in buffer[0..numCharsRead-1] }

This reduces the number of I/O operations and is more practical for processing file content in chunks.

Handling Character Encoding with FileReader

The biggest limitation of FileReader is that its single-argument constructors use the default charset of the JVM. If the file uses a different encoding, the characters will be decoded incorrectly. For example, a file saved as UTF-8 on a system where the default charset is ISO-8859-1 will produce garbled output for non-ASCII characters.

On Java 18 and later, the JVM default charset is UTF-8 by default, but it can be configured differently. Passing an explicit charset remains safer when the file encoding matters.

To control the encoding explicitly, use InputStreamReader with a FileInputStream:

import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; try (Reader reader = new InputStreamReader( new FileInputStream("example.txt"), StandardCharsets.UTF_8)) { // read characters }

If you are on Java 11 or later, FileReader also has constructors that accept a Charset, so you can use it directly:

import java.io.FileReader; import java.nio.charset.StandardCharsets; try (FileReader reader = new FileReader("example.txt", StandardCharsets.UTF_8)) { // read characters }

The InputStreamReader form works on all Java versions. The FileReader charset constructors require Java 11 or later. If you are working with UTF-8 files, which is common in modern applications, pass StandardCharsets.UTF_8 explicitly instead of relying on the default charset.

Using BufferedReader for Efficient Line Reading

FileReader itself does not buffer input. Each call to read() triggers a read from the underlying file, which can be expensive. For text files that are read line by line, the standard pattern is to wrap FileReader in a BufferedReader.

import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } }

BufferedReader provides a readLine() method that returns a line without the newline characters, or null when the end of the file is reached. The internal buffer reduces the number of underlying I/O calls, which improves performance for typical text processing.

You can also use BufferedReader with InputStreamReader to combine explicit encoding with buffering:

try (BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream("example.txt"), StandardCharsets.UTF_8))) { String line; while ((line = br.readLine()) != null) { // process line } }

This is the most flexible and efficient approach for reading text files in Java.

Closing Resources and Handling Exceptions

FileReader, like all I/O classes, must be closed after use to release the underlying file handle. Failing to close a reader can lead to resource leaks and prevent other processes from accessing the file.

The modern way to handle this is the try-with-resources statement, which automatically closes the reader at the end of the block, even if an exception occurs.

try (FileReader reader = new FileReader("example.txt")) { // read data } catch (IOException e) { // handle I/O error }

If you are using a BufferedReader, closing it also closes the underlying FileReader, so you only need to close the outermost stream.

The String and File constructors without a charset throw FileNotFoundException if the file is missing, and the read() methods throw IOException for I/O errors. Catching IOException covers both cases if you include the constructor inside the try block. However, if you need to distinguish a missing file from other errors, you can catch FileNotFoundException separately.

Common Mistakes and Compatibility Notes

One common mistake is assuming that FileReader uses UTF-8 by default. It does not: the single-argument constructors use the JVM's default charset, which can vary across systems and runtime configurations (on Java 18+ it defaults to UTF-8 unless changed). This can cause data corruption when the file encoding differs.

Another mistake is using FileReader for binary files. FileReader is a character stream, so it decodes bytes into characters. For binary data, use FileInputStream directly.

FileReader has been part of Java since version 1.1. Before Java 11, it did not support explicit charsets, which is why many developers still prefer InputStreamReader for code that must handle encodings. If you are on Java 11+, the FileReader(File, Charset) and FileReader(String, Charset) constructors give you encoding control directly.

Performance Considerations for Large Files

Reading a file with FileReader without buffering is inefficient because each read() call performs a native I/O operation. For large files, this can result in thousands of system calls and poor performance.

Using BufferedReader reduces the number of I/O operations by reading a large chunk of data into memory at once. The default buffer size is 8192 characters, which is adequate for most use cases. If you need to process very large files, you can also consider using Files.newBufferedReader from the java.nio.file package, which returns a BufferedReader with a configurable charset.

import java.nio.file.Files; import java.nio.file.Path; import java.nio.charset.StandardCharsets; import java.io.BufferedReader; try (BufferedReader br = Files.newBufferedReader( Path.of("example.txt"), StandardCharsets.UTF_8)) { String line; while ((line = br.readLine()) != null) { // process line } }

This approach combines the convenience of a buffered reader with explicit encoding control. Path.of and Files.readString require Java 11 or later. When you need to read the entire file into memory, Files.readString is even simpler, but it is only suitable for files that fit comfortably in memory.

Java FileReader: Practical Usage and Code Examples | RYUSLOG DEV