FileReader
FileReader is a convenience class for reading character files, extending InputStreamReader with a FileInputStream underneath. It opens a file and decodes its bytes into characters using the platform's default charset (or, since Java 11, an explicitly specified charset). The critical distinction between FileReader and FileInputStream is the abstraction level: FileInputStream reads raw bytes; FileReader reads decoded characters, handling the byte-to-character conversion transparently. Before Java 11, FileReader offered no constructor accepting an explicit charset — it silently used the JVM's default charset, which differs between platforms and locales, making it unsuitable for portable applications. Since Java 11, FileReader(File, Charset) and FileReader(String, Charset) constructors allow explicit charset specification, resolving this issue. FileReader is unbuffered — every read() call ultimately results in a system call — so it is almost always wrapped in a BufferedReader for practical use. This entry covers all constructors and their charset behavior, the read contract including return values and end-of-file, the practical wrapping pattern, and when to prefer InputStreamReader or NIO's Files.newBufferedReader over FileReader.
Constructors, Charset Behavior, and the Pre-Java-11 Trap
// ── Pre-Java-11 constructors: implicit platform default charset ────────
// DANGEROUS on Windows where default may be Cp1252, not UTF-8:
FileReader legacy1 = new FileReader("file.txt"); // platform default
FileReader legacy2 = new FileReader(new File("file.txt")); // platform default
// Both read UTF-8 files incorrectly on Windows if platform default != UTF-8
// ── Java 11+: explicit charset constructors ────────────────────────────
import java.nio.charset.StandardCharsets;
FileReader utf8Reader = new FileReader("file.txt", StandardCharsets.UTF_8);
FileReader latin1Reader = new FileReader(new File("file.txt"), StandardCharsets.ISO_8859_1);
// Charset is explicit — no platform-dependency
// ── What platform default charset is on this JVM ─────────────────────
System.out.println(Charset.defaultCharset()); // e.g., "UTF-8" or "windows-1252"
// Add -Dfile.encoding=UTF-8 JVM arg to force UTF-8 when platform default is wrong
// ── Reading characters one at a time ─────────────────────────────────
try (FileReader fr = new FileReader("text.txt", StandardCharsets.UTF_8)) {
int ch;
while ((ch = fr.read()) != -1) { // read() returns int: 0-65535 for char, -1 for EOF
System.out.print((char) ch); // cast int to char for use
}
}
// ── Reading into a char array ─────────────────────────────────────────
try (FileReader fr = new FileReader("text.txt", StandardCharsets.UTF_8)) {
char[] buffer = new char[1024];
int charsRead;
StringBuilder sb = new StringBuilder();
while ((charsRead = fr.read(buffer, 0, buffer.length)) != -1) {
sb.append(buffer, 0, charsRead); // append only the chars actually read
}
System.out.println(sb.toString());
}
// ── The -1 return and end-of-file contract ────────────────────────────
try (FileReader fr = new FileReader("empty.txt", StandardCharsets.UTF_8)) {
int ch = fr.read();
System.out.println(ch); // -1 immediately — file is empty
}
// ── Preferred alternative 1: Files.newBufferedReader ─────────────────
// Cleaner, always buffered, explicit charset, idiomatic modern Java:
try (BufferedReader br = Files.newBufferedReader(
Path.of("text.txt"), StandardCharsets.UTF_8)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// ── Preferred alternative 2: InputStreamReader for flexibility ────────
// Allows wrapping any InputStream (network, classpath, etc.):
InputStream is = getClass().getResourceAsStream("/data/config.txt");
try (BufferedReader br = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}Practical Usage — Wrapping with BufferedReader
// ── The mandatory pattern: FileReader wrapped in BufferedReader ────────
try (BufferedReader br = new BufferedReader(
new FileReader("document.txt", StandardCharsets.UTF_8))) {
String line;
int lineNumber = 0;
while ((line = br.readLine()) != null) {
lineNumber++;
System.out.printf("%4d: %s%n", lineNumber, line);
}
}
// ── readLine() return value contract ─────────────────────────────────
try (BufferedReader br = new BufferedReader(
new FileReader("data.csv", StandardCharsets.UTF_8))) {
String line = br.readLine(); // first line (no trailing
or
)
if (line == null) {
System.out.println("File is empty");
} else {
System.out.println("First line: " + line);
// line does NOT include the line terminator
}
}
// ── Line terminators: readLine() handles all three ────────────────────
//
(Unix/Linux/macOS)
//
(Windows)
//
(old Classic Mac OS)
// readLine() handles all three transparently — no need to strip terminators
// ── Processing CSV with BufferedReader wrapping FileReader ─────────────
List<String[]> records = new ArrayList<>();
try (BufferedReader br = new BufferedReader(
new FileReader("data.csv", StandardCharsets.UTF_8))) {
String line;
br.readLine(); // skip header line
while ((line = br.readLine()) != null) {
if (!line.isBlank()) { // skip empty lines
records.add(line.split(",", -1)); // split on comma, keep trailing empty fields
}
}
}
// ── Modern alternative: Files.newBufferedReader ───────────────────────
// Exactly equivalent to new BufferedReader(new FileReader(path, charset))
// but shorter and more idiomatic:
try (BufferedReader br = Files.newBufferedReader(
Path.of("document.txt"), StandardCharsets.UTF_8)) {
br.lines() // Stream<String> of lines
.filter(line -> !line.isBlank())
.map(String::trim)
.forEach(System.out::println);
}
// ── Stream<String> via lines() — lazy line reading ────────────────────
try (BufferedReader br = Files.newBufferedReader(
Path.of("large.log"), StandardCharsets.UTF_8)) {
long errorCount = br.lines()
.filter(line -> line.contains("ERROR"))
.count();
System.out.println("Errors: " + errorCount);
} // BufferedReader closed when try-with-resources exits — stream also closed
// ── ready() for non-blocking check (rarely needed) ───────────────────
try (BufferedReader br = new BufferedReader(
new FileReader("file.txt", StandardCharsets.UTF_8))) {
if (br.ready()) {
System.out.println("Data available: " + br.readLine());
}
// ready() on a file with BufferedReader: true if buffer has data or file has more
// NOT equivalent to "has more lines" — don't use as loop condition
}