📂 Java File I/O — Reading and Writing Files with java.nio
Master Java file I/O — reading and writing files with Files, Path and BufferedReader, the modern java.nio API, try-with-resources, and streams vs readers. With examples.
Java has two I/O generations: the legacy java.io streams and the modern, cleaner java.nio.file (Files/Path). Use NIO for new code.
🆕 The modern way — Files + Path
Path path = Path.of("data.txt");
// Read all lines (small files)
List<String> lines = Files.readAllLines(path);
// Read entire file as a String
String content = Files.readString(path);
// Write
Files.writeString(path, "hello\n");🌊 Streaming large files
For big files, stream line-by-line so you don't load everything into memory:
try (Stream<String> stream = Files.lines(path)) {
long count = stream.filter(l -> l.contains("ERROR")).count();
}📖 Buffered readers (legacy but common)
try (BufferedReader r = Files.newBufferedReader(path)) {
String line;
while ((line = r.readLine()) != null) process(line);
}🔤 Byte vs character streams
- Byte streams (
InputStream/OutputStream) — raw bytes: images, binary. - Character streams (
Reader/Writer) — text with encoding (UTF-8).
💡 Always use try-with-resources
File handles are scarce OS resources. try (...) closes them automatically, even on exception — never leak a file descriptor.
💻 Code Examples
Read a whole file (modern)
Path p = Path.of("notes.txt");
String text = Files.readString(p);
System.out.println(text.lines().count() + " lines");Output:
Prints the number of lines in notes.txt.
Stream a large file safely
try (Stream<String> lines = Files.lines(Path.of("app.log"))) {
long errors = lines.filter(l -> l.contains("ERROR")).count();
System.out.println(errors + " errors");
}Output:
Counts ERROR lines without loading the whole file.
Write and append
Path p = Path.of("out.txt");
Files.writeString(p, "first line\n");
Files.writeString(p, "appended\n", StandardOpenOption.APPEND);Output:
Creates out.txt, then appends a second line.
⚠️ Common Mistakes
- Not using try-with-resources — leaks file descriptors and may keep files locked.
- Reading huge files with readAllLines/readString — loads everything into memory; stream instead.
- Ignoring character encoding — always specify UTF-8 to avoid platform-default surprises.
- Using legacy FileReader without buffering — slow; use Files.newBufferedReader.
🎯 Interview Questions
Real questions asked at top product and service-based companies.
Q1.What's the difference between byte streams and character streams?Beginner
Byte streams (InputStream/OutputStream) handle raw 8-bit bytes for binary data like images. Character streams (Reader/Writer) handle text and apply a character encoding (e.g., UTF-8) to convert bytes to characters.
Q2.Why prefer java.nio.file (Files/Path) over java.io (File)?Intermediate
NIO offers a cleaner API (Files.readString, Files.lines), better error messages, symbolic-link and metadata support, efficient streaming, and built-in UTF-8 handling — far less boilerplate than the legacy File/stream classes.
Q3.How do you read a large file without running out of memory?Intermediate
Stream it line by line with Files.lines(path) (in a try-with-resources) or a BufferedReader loop, processing each line as you go instead of loading the whole file with readAllLines/readString.
Q4.Why use try-with-resources for file operations?Beginner
File handles are limited OS resources. try-with-resources guarantees the stream/reader is closed automatically when the block exits — even on exception — preventing resource leaks and file locks.
🧠 Quick Summary
- Prefer java.nio.file (Files, Path) over legacy java.io.
- Files.readString / readAllLines for small files.
- Files.lines (streaming) for large files — low memory.
- Byte streams for binary; character streams (Reader/Writer) for text.
- Always use try-with-resources and specify UTF-8.