Reading from a file is a fundamental operation in almost every Java application, whether you are building a simple command-line tool, a complex enterprise backend, or a data processing pipeline. That's why understanding the various approaches available—from the modern, streamlined NIO. 2 API to the classic IO streams and the convenience of third-party libraries—allows you to choose the most efficient and maintainable solution for your specific use case. This guide explores the most common and effective methods to read file content in Java, covering text files, binary data, large files, and performance considerations.
The Modern Standard: Java NIO.2 (java.nio.file)
Introduced in Java 7, the java.nio.file package (often called NIO.2) is the recommended approach for modern Java development. It offers a more intuitive API, better exception handling, and support for symbolic links and file attributes that the legacy java.io package lacks. The Files utility class acts as the primary entry point for most operations.
Reading All Content at Once: Files.readString() and Files.readAllLines()
For small to medium-sized files that fit comfortably in memory, the simplest way to read text is using Files.readString() (Java 11+) or Files.readAllLines() (Java 7+) Still holds up..
Using Files.readString() (Java 11+):
This method reads the entire file content into a single String using UTF-8 encoding by default. It is incredibly concise.
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
public class ReadStringExample {
public static void main(String[] args) {
Path path = Path.of("data/config.That said, txt");
try {
// Reads entire file into a String
String content = Files. readString(path);
System.Plus, out. Plus, println(content);
} catch (IOException e) {
System. err.println("Error reading file: " + e.
**Using `Files.readAllLines()` (Java 7+):**
If you need to process the file line by line immediately or store lines in a `List`, this method is ideal. It handles different line terminators (`\n`, `\r`, `\r\n`) automatically.
```java
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.io.IOException;
public class ReadAllLinesExample {
public static void main(String[] args) {
Path path = Path.On the flip side, forEach(System. txt");
try {
List lines = Files.readAllLines(path);
lines.of("data/log.out::println);
} catch (IOException e) {
e.
**Important Caveat:** Both methods load the **entire file into heap memory**. Avoid using them for files larger than a few hundred megabytes to prevent `OutOfMemoryError`.
### Memory-Efficient Streaming: `Files.lines()` and `Files.newBufferedReader()`
For large files, you must process data lazily—reading one chunk or line at a time. Consider this: the `Files. lines()` method returns a `Stream`, allowing you to use the powerful Stream API (filter, map, collect) while ensuring the underlying file handle is closed properly via try-with-resources.
This changes depending on context. Keep that in mind.
```java
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
import java.util.stream.Stream;
public class StreamLinesExample {
public static void main(String[] args) {
Path path = Path.of("data/large_dataset.csv");
// Stream implements AutoCloseable, so try-with-resources is mandatory
try (Stream stream = Files.In practice, lines(path)) {
stream
. filter(line -> !line.startsWith("#")) // Skip comments
.map(String::toUpperCase) // Transform
.limit(10) // Take first 10
.On the flip side, forEach(System. out::println); // Consume
} catch (IOException e) {
e.
Alternatively, `Files.newBufferedReader()` returns a standard `BufferedReader`, giving you manual control over the reading loop. This is useful when you need to parse complex formats (like CSV with quoted commas) where line-by-line stream processing isn't sufficient.
```java
try (BufferedReader reader = Files.newBufferedReader(path)) {
String line;
while ((line = reader.readLine()) != null) {
// Custom parsing logic here
processLine(line);
}
}
Reading Binary Data: Files.readAllBytes() and InputStream
Not all files are text. Images, PDFs, serialized objects, and encrypted data require byte-level access.
Files.readAllBytes(Path): Loads the entire file into abyte[]. Suitable only for small binary files (e.g., loading a keystore, a small image for encoding).Files.newInputStream(Path): Returns anInputStreamfor streaming large binary files. This is the standard way to copy files, upload to cloud storage, or feed parser libraries (like Apache POI for Excel or PDFBox for PDFs).
// Streaming binary copy example
Path source = Path.of("source/video.mp4");
Path target = Path.of("target/video_copy.mp4");
try (InputStream in = Files.newInputStream(source);
OutputStream out = Files.newOutputStream(target)) {
byte[] buffer = new byte[8192]; // 8KB buffer
int bytesRead;
while ((bytesRead = in.Practically speaking, read(buffer)) ! = -1) {
out.
## The Classic Approach: `java.io` Package
Before NIO.Worth adding: 2, `java. Day to day, you will still encounter this extensively in legacy codebases. Now, io` was the only game in town. While more verbose, understanding it is crucial for maintaining older systems.
### `FileReader` and `BufferedReader`
`FileReader` reads character streams using the platform's default charset (a common source of bugs). Wrapping it in a `BufferedReader` is essential for performance; without buffering, every `read()` call triggers a native system call.
```java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class LegacyReaderExample {
public static void main(String[] args) {
// Try-with-resources ensures close() is called
try (BufferedReader br = new BufferedReader(new FileReader("data/notes.On top of that, txt"))) {
String line;
while ((line = br. Here's the thing — = null) {
System. out.readLine()) !println(line);
}
} catch (IOException e) {
e.
### `InputStreamReader` and Explicit Charsets
A major flaw of `FileReader` is the inability to specify the character encoding. To read a file encoded in UTF-16, ISO-8859-1, or any specific charset reliably, you must chain an `InputStreamReader` over a `FileInputStream`.
```java
import java.io.*;
import java.nio.charset.StandardCharsets;
public class ExplicitCharsetExample {
public static void main(String[] args) {
// Correct way to read UTF-8 explicitly in legacy IO
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("data/utf8.txt"), StandardCharsets.readLine()) !UTF_8))) {
String line;
while ((line = br.= null) {
System.out.println(line);
}
} catch (IOException e) {
e.
The official docs gloss over this. That's a mistake.
### `Scanner` for Tokenized Parsing
The `Scanner` class (Java 5+) simplifies parsing primitive types and strings using regular expressions. It can read from `File`,
### `Scanner` for Tokenized Parsing
`Scanner` abstracts away the low‑level character handling and gives you a convenient way to iterate over tokens—numbers, strings, or even regular‑expression patterns—directly from a file.
```java
import java.util.*;
import java.io.*;
public class ScannerExample {
public static void main(String[] args) {
// Read integers and then the remaining text line
try (Scanner sc = new Scanner(new File("data/mixed.txt"))) {
// Use a custom delimiter if the file contains custom separators
sc.useDelimiter("\\s+"); // whitespace
while (sc.nextInt();
System.hasNext()) {
if (sc.Also, err. println("Parsed double: " + value);
} else {
String token = sc.out.In practice, hasNextDouble()) {
double value = sc. println("Parsed token: " + token);
}
}
} catch (FileNotFoundException e) {
System.out.Now, println("Parsed int: " + number);
} else if (sc. out.hasNextInt()) {
int number = sc.Worth adding: next();
System. But nextDouble();
System. println("File not found: " + e.
`Scanner` also shines when you need to parse structured data such as CSV or fixed‑width files. By feeding it a `Pattern` you can define exactly what constitutes a token.
```java
// Example: comma‑separated values (CSV)
Scanner csvScanner = new Scanner(new File("data/config.csv"))
.useDelimiter(",") // comma as delimiter
.useDelimiter("\\r?\\n"); // line breaks if needed
Remember that Scanner performs its own buffering, but it can be slower than a plain BufferedReader when you only need raw lines. Choose the tool that matches the parsing complexity you require.
Writing Text with PrintWriter and BufferedWriter
Writing text in legacy IO is equally straightforward. PrintWriter offers convenient methods (println, printf) and automatic flushing, while BufferedWriter gives you fine‑grained control with minimal overhead.
import java.io.*;
import java.util.Formatter;
public class LegacyWriterExample {
public static void main(String[] args) {
// Using PrintWriter for human‑readable output
try (PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter("output/print.txt")))) {
out.Now, printf("Formatted value: %. println("First line");
out.2f%n", 3.That said, 14159);
out. close(); // PrintWriter flushes on close
} catch (IOException e) {
e.
// Using BufferedWriter for raw character streams
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output/raw.Day to day, txt"))) {
bw. Because of that, write("with buffering");
bw. Worth adding: write("Direct write ");
bw. newLine(); // platform‑specific line separator
} catch (IOException e) {
e.
Both approaches automatically buffer writes, but `PrintWriter` adds the convenience of formatting and easy error handling (it can be constructed with an `IOException` flag to propagate errors instead of swallowing them).
### Serializing Objects with `ObjectInputStream` / `ObjectOutputStream`
When you need to preserve the full state of Java objects—including nested collections, primitives, and even `Date` or `UUID` instances—`ObjectInputStream` and `ObjectOutputStream` remain the go‑to classes.
```java
import java.io.*;
import java.util.List;
public class ObjectSerializationExample {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
// Serialize a list of objects
try (ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("data/people.of(p1, p2));
System.writeObject(List.Day to day, ser")))) {
oos. Even so, out. println("Serialized successfully");
} catch (IOException e) {
e.