File Handling & I/O
Java offers two generations of file APIs: the original java.io.File from JDK 1.0 and the modern java.nio.file package (NIO.2) introduced in Java 7. New code should prefer NIO.2 — it is more capable, more consistent, and integrates cleanly with streams and try-with-resources.
Legacy: java.io.File
File represents a path on the filesystem (the file may or may not exist). It mixes path manipulation with limited operations and reports failures via boolean return values rather than exceptions.
import java.io.File;
File file = new File("notes.txt");
System.out.println(file.exists());
System.out.println(file.getAbsolutePath());
System.out.println(file.length());
file.delete(); // returns false on failure, no reason given
Warning:
Filemethods likedelete()andmkdir()swallow the cause of failure, returning onlyfalse. NIO.2 throws descriptive exceptions instead — a key reason to migrate.
Modern: Path and Files
Path replaces File for representing locations; Files provides static methods for every operation. Create a Path with Path.of (or Paths.get).
import java.nio.file.*;
Path path = Path.of("data", "reports", "q2.txt");
System.out.println(path.getFileName()); // q2.txt
System.out.println(path.getParent()); // data/reports
System.out.println(path.toAbsolutePath());
boolean exists = Files.exists(path);
Files covers existence checks, copying, moving, deleting, attributes, and directory walking — all throwing IOException with a clear cause.
Reading Text
For small files, Files.readString and Files.readAllLines are the simplest options.
import java.nio.file.*;
import java.util.List;
Path path = Path.of("data.txt");
String whole = Files.readString(path); // entire file as String
List<String> lines = Files.readAllLines(path); // List<String>
System.out.println(lines.size() + " lines");
Warning:
readStringandreadAllLinesload the whole file into memory. For large files, stream the lines lazily instead.
Streaming Lines Lazily
Files.lines returns a lazy Stream<String> that reads on demand and must be closed — wrap it in try-with-resources.
import java.nio.file.*;
import java.util.stream.Stream;
Path path = Path.of("access.log");
try (Stream<String> lines = Files.lines(path)) {
long errors = lines
.filter(l -> l.contains("ERROR"))
.count();
System.out.println("errors: " + errors);
}
This processes a multi-gigabyte log without ever holding it all in memory.
Writing Text
Files.writeString and Files.write handle small writes in one call. Use StandardOpenOption to control behavior.
import java.nio.file.*;
import java.util.List;
Path path = Path.of("out.txt");
Files.writeString(path, "first line\n"); // creates or truncates
Files.writeString(path, "appended\n",
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
Files.write(path, List.of("line A", "line B")); // each on its own line
BufferedReader and BufferedWriter
For fine-grained, high-throughput streaming I/O, use buffered readers and writers obtained from Files. Buffering minimizes system calls.
import java.nio.file.*;
import java.io.*;
Path in = Path.of("input.txt");
Path out = Path.of("output.txt");
try (BufferedReader reader = Files.newBufferedReader(in);
BufferedWriter writer = Files.newBufferedWriter(out)) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line.strip());
writer.newLine();
}
}
Both resources are declared in the same try-with-resources and closed in reverse order automatically.
| Task | Best API |
|---|---|
| Small file → String | Files.readString |
| Small file → lines | Files.readAllLines |
| Large file, lazy | Files.lines (in try-with-resources) |
| Streaming line-by-line | Files.newBufferedReader |
| Write small content | Files.writeString / Files.write |
| Streaming write | Files.newBufferedWriter |
Note: Always specify a
Charset(default is UTF-8 for these methods in modern JDKs) when text encoding matters; relying on the platform default causes portability bugs.
Best Practices
- Prefer
java.nio.file(Path,Files) over legacyjava.io.Filein new code. - Wrap every stream-returning method (
Files.lines, buffered readers/writers) in try-with-resources. - Use
Files.linesfor large files to avoid loading everything into memory. - Be explicit about character encoding to ensure cross-platform correctness.
- Build paths with
Path.of/resolverather than string concatenation with separators. - Handle
IOExceptionmeaningfully — log the failing path and cause, don’t silently ignore.