Intermediate⏱️ 9 min📘 Topic 21 of 22

📂 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.