1. Reading a file line by line: BufferedReader and friends
Previously we looked at byte-by-byte reading: it’s convenient for working with binary formats, but for text it’s awkward. A file consists of characters that depend on the charset. That’s why Java offers convenient “wrappers” — FileReader, BufferedReader, and other classes that turn a stream of bytes into a stream of characters and lines.
Imagine a text file — whether it’s a program log, a list of users, or even the huge novel “War and Peace”. Sometimes you need to quickly read the entire file, sometimes to go through it line by line, and sometimes to extract one specific line.
In Java there are several ways to do this, and the choice depends on the file size and the task. If you need to process a file one line at a time (for example, count lines or find an entry), use line-by-line reading. And if the file is small, you can load it entirely into memory and work with it as a list of lines.
Why is line-by-line reading good?
For large files, line-by-line reading saves you from memory problems. Loading a gigabyte-size log into memory is a bad idea — you’re likely to hit an OutOfMemoryError. Reading the file line by line, however, can be done with minimal overhead, even if it’s hundreds of megabytes.
How is it done in Java?
The most classic way is to use BufferedReader (or its relatives) and the readLine() method.
import java.io.*;
public class ReadLinesDemo {
public static void main(String[] args) {
String fileName = "example.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
System.out.printf("%3d: %s%n", lineNumber, line);
lineNumber++;
}
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
}
}
}
Here we open the file for reading and use readLine() to read one line at a time until we reach the end (null). After that we print each line with its number.
Briefly about try-with-resources
See the try (...) { ... } construct? That’s try-with-resources. It guarantees that the file will be closed even if an error occurs in the middle of reading. You don’t need to call close() manually: closing happens automatically, even with catch/finally.
Why do you need BufferedReader?
BufferedReader reads not one character at a time, but in blocks (usually 8192 bytes), which speeds up file I/O. In addition, it has a convenient readLine() method that returns a line up to the line break.
What buffer size should you choose?
Typically, BufferedReader uses a buffer of 8192 bytes (8 KB) — that’s enough for most tasks. If you read very long lines (for example, about 100_000 characters), you can increase the buffer:
BufferedReader reader = new BufferedReader(new FileReader(fileName), 65536); // 64 KB buffer
But for typical tasks, the default size is perfect.
2. Reading an entire file: Files.readAllLines and Files.readString
If the file is small (for example, up to 10–20 MB), it’s convenient to load it all at once. For example, if you need to quickly get the list of lines, analyze the text, or send it over the network.
Modern way: Files.readAllLines
Since Java 7, the convenient Files class appeared with lots of useful methods.
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class ReadAllLinesDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try {
List<String> lines = Files.readAllLines(path);
for (int i = 0; i < lines.size(); i++) {
System.out.printf("%3d: %s%n", i + 1, lines.get(i));
}
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
}
}
}
Here the Files.readAllLines(path) method returns a list of lines (List<String>). You can work with this list like a regular collection: search, sort, filter.
Newer: Files.readString (Java 11+)
If you need the whole file as a single string (for example, to search for a substring or to send as JSON), use Files.readString:
import java.nio.file.*;
import java.io.IOException;
public class ReadStringDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try {
String content = Files.readString(path);
System.out.println("File contents:");
System.out.println(content);
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
}
}
}
What about character encoding?
By default, Files.readAllLines and Files.readString use your platform default charset. If the file is encoded differently (for example, Windows-1251), specify it explicitly:
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
String content = Files.readString(path, StandardCharsets.UTF_8);
3. Comparison of approaches: when to use which
| Approach | When to use | Advantages | Disadvantages |
|---|---|---|---|
|
Large files, line-by-line processing | Memory-efficient, flexible | Only line-based reading |
|
Small and medium files | Immediate list of lines, simple | For large files — OutOfMemoryError |
|
Small files, need the whole text | Entire content as a single string | No line splitting |
Recommendation:
— If the file is small — use Files.readAllLines or Files.readString.
— If the file is large or you don’t know its size — use BufferedReader.readLine().
4. Practical tasks: examples and walkthrough
Example 1. Counting lines in a large file
Suppose you need to know how many lines are in a huge file (for example, a server log).
import java.io.*;
public class LineCount {
public static void main(String[] args) {
String fileName = "biglog.txt";
int lineCount = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
while (reader.readLine() != null) {
lineCount++;
}
System.out.println("Total lines in file: " + lineCount);
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
}
}
}
Example 2. Searching lines by content
Find all lines that contain the word “error” (case-insensitive):
import java.io.*;
public class FindErrorLines {
public static void main(String[] args) {
String fileName = "biglog.txt";
String keyword = "error";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
if (line.toLowerCase().contains(keyword)) {
System.out.printf("%3d: %s%n", lineNumber, line);
}
lineNumber++;
}
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
}
}
}
Example 3. Loading configuration from a small file
File config.txt:
host=localhost
port=8080
mode=dev
Read it entirely and parse into key–value pairs:
import java.nio.file.*;
import java.util.*;
public class ConfigLoader {
public static void main(String[] args) throws Exception {
Path path = Path.of("config.txt");
List<String> lines = Files.readAllLines(path);
Map<String, String> config = new HashMap<>();
for (String line : lines) {
if (line.trim().isEmpty() || line.startsWith("#")) continue; // skip empty lines and comments
String[] parts = line.split("=", 2);
if (parts.length == 2) {
config.put(parts[0].trim(), parts[1].trim());
}
}
System.out.println("Loaded configuration: " + config);
}
}
5. Common mistakes when reading text files
Mistake #1: Attempting to read a binary file as text. If you open an image or archive via BufferedReader or Files.readAllLines, you’ll get garbage and risk an OutOfMemoryError. For binary files use InputStream!
Mistake #2: Not handling exceptions. Files can be deleted, moved, or locked. Always wrap reading in try-catch and inform the user about problems.
Mistake #3: Ignoring character encoding. If your text is in Russian and you read the file without specifying the charset, you can get “?????”. Use StandardCharsets.UTF_8 or the charset you need.
Mistake #4: Forgetting to close the stream. If you don’t close a file, it can remain locked until the program ends. Always use try-with-resources.
Mistake #5: Using read() instead of readLine() for text files. The read() method reads one character at a time — it’s slow and inconvenient for lines. For text, use readLine() or the Files methods.
GO TO FULL VERSION