File API

Java file I/O — the modern java.nio.file.Files API vs legacy java.io.File, reading, writing, and traversing the filesystem.

Last reviewed: January 2025

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: Java has two file APIs: the legacy java.io.File (Java 1.0, avoid in new code) and the modern java.nio.file package (Java 7+). Use Path and Files from java.nio.file — they are more expressive, throw better exceptions, support symbolic links, and have a rich utility API. Always use try-with-resources for file operations.

🔍 What Is It?

Modern API (use this):

  • →java.nio.file.Path — represents a file or directory path (replaces java.io.File)
  • →java.nio.file.Files — utility class with all file operations
  • →java.nio.file.Paths — factory for Path objects (Java 7-10)
  • →Path.of() — factory for Path objects (Java 11+, preferred)

Legacy API (avoid in new code):

  • →java.io.File — old path representation
  • →java.io.FileInputStream/FileOutputStream — byte streams
  • →java.io.FileReader/FileWriter — character streams (no charset control!)
  • →java.io.BufferedReader/BufferedWriter — buffered wrappers

Key Files methods:

  • →readString(path) / readAllBytes(path) / readAllLines(path) — read entire file
  • →writeString(path, content) / write(path, bytes) — write entire file
  • →lines(path) — Stream<String> of lines (lazy)
  • →copy(source, target) / move(source, target) / delete(path)
  • →createFile(path) / createDirectory(path) / createDirectories(path)
  • →exists(path) / isDirectory(path) / isRegularFile(path)
  • →walk(path) — Stream<Path> of all files recursively
  • →list(path) — Stream<Path> of immediate children

🎯 Why Do We Need It?

The legacy java.io.File API has many problems:

  • →Methods return false instead of throwing exceptions (hard to diagnose failures)
  • →No charset control in FileReader/FileWriter (uses platform default — dangerous!)
  • →No atomic operations
  • →No symbolic link support
  • →No file metadata (owner, permissions, timestamps)

java.nio.file solves all of these:

  • →Throws IOException with clear messages
  • →Explicit charset everywhere
  • →StandardCopyOption.ATOMIC_MOVE for atomic file replacement
  • →Full symbolic link and attribute support

🏗️ What Happens in Real Projects?

Common enterprise use cases:

  • →Reading configuration files (YAML, JSON, properties)
  • →Writing reports and exports (CSV, PDF, Excel)
  • →Processing uploaded files
  • →Log file analysis
  • →File-based data pipelines

⚙️ How It Works

Path vs File:

// Legacy
File file = new File("/data/config.json");

// Modern (preferred)
Path path = Path.of("/data/config.json");
Path path2 = Paths.get("/data", "config.json");  // same result

Buffering:

Files.readString() reads the entire file into memory — fine for small files. For large files, use Files.lines() (returns a Stream) or BufferedReader for line-by-line processing without loading everything into memory.

Character encoding:

Always specify charset explicitly. Never use FileReader/FileWriter — they use the platform default charset which varies by OS.

💻 Example

File Operations in Enterprise Java — Common file I/O patterns used in production applications.

java
// Reading entire file (small files)
Path configPath = Path.of("/etc/myapp/config.json");
String content = Files.readString(configPath, StandardCharsets.UTF_8);

// Reading all lines (small-medium files)
List<String> lines = Files.readAllLines(configPath, StandardCharsets.UTF_8);

// Reading line by line (large files — lazy, memory-efficient)
try (Stream<String> lines = Files.lines(configPath, StandardCharsets.UTF_8)) {
    lines.filter(line -> !line.startsWith("#"))  // skip comments
         .map(String::trim)
         .filter(line -> !line.isEmpty())
         .forEach(this::processConfigLine);
}  // stream closed automatically

// Writing a file (creates or overwrites)
String report = generateReport();
Files.writeString(
    Path.of("/reports/daily-report.txt"),
    report,
    StandardCharsets.UTF_8,
    StandardOpenOption.CREATE,
    StandardOpenOption.TRUNCATE_EXISTING
);

// Appending to a file
Files.writeString(
    Path.of("/logs/audit.log"),
    "User login: " + userId + "
",
    StandardCharsets.UTF_8,
    StandardOpenOption.CREATE,
    StandardOpenOption.APPEND
);

// Writing bytes
byte[] pdfBytes = generatePdf();
Files.write(Path.of("/exports/report.pdf"), pdfBytes);

// Atomic file replacement (safe for config files)
Path tempPath = Path.of("/etc/myapp/config.json.tmp");
Path finalPath = Path.of("/etc/myapp/config.json");
Files.writeString(tempPath, newConfig, StandardCharsets.UTF_8);
Files.move(tempPath, finalPath, StandardCopyOption.ATOMIC_MOVE,
           StandardCopyOption.REPLACE_EXISTING);

🤔 Common Confusions

❓ What is the difference between Path and File?

Path (java.nio.file.Path) is the modern representation of a file system path — use it for all new code. File (java.io.File) is the legacy class from Java 1.0. Path has better exception handling, supports symbolic links, and has a richer API. Convert between them: file.toPath() and path.toFile() when interoperating with legacy APIs.

❓ When should I use Files.readString() vs Files.lines()?

Files.readString() loads the entire file into a String in memory — use for small files (< 10MB). Files.lines() returns a lazy Stream<String> — use for large files where you do not need all lines in memory at once. Always close the stream from Files.lines() with try-with-resources.

🚨 Common Production Issues

File handle leak — not closing streams and readers leads to "too many open files" errors. Always use try-with-resources.

Platform-dependent line endings — files created on Windows have \r\n; Linux has \n. Use Files.readAllLines() which handles both, or normalize with line.stripTrailing().

Charset issues — reading a UTF-8 file without specifying charset may use the platform default (not UTF-8 on some Windows systems). Always specify StandardCharsets.UTF_8.

✅ Best Practices

Use Path.of() and Files utility class for all file operations.

Always specify charset explicitly — StandardCharsets.UTF_8 for most cases.

Always use try-with-resources for streams and readers.

Use Files.lines() for large files — do not load entire large files into memory.

Use atomic file replacement (write to temp, then move) for config files.

Use Files.createTempFile() for temporary files and delete them in finally.

🏛️ Architect's Note

Senior Architect's Perspective

For enterprise file processing (large CSV imports, bulk exports), avoid loading entire files into memory. Use Files.lines() for streaming line-by-line processing, or Apache Commons CSV / OpenCSV for CSV parsing. For Excel files, use Apache POI with streaming API (SXSSFWorkbook). For very large files (GBs), consider Java NIO channels with ByteBuffers for maximum performance.

💬 Frequently Asked Questions

How do I watch a directory for file changes?

Use WatchService (java.nio.file): WatchService watcher = FileSystems.getDefault().newWatchService(); path.register(watcher, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); then poll with watcher.take() in a loop. This is OS-native (inotify on Linux, FSEvents on macOS) and efficient. Spring Integration and Apache Camel provide higher-level file watching abstractions.

🎯 Key Takeaways

1Use Path and Files (java.nio.file) for all new code — not java.io.File
2Always specify charset: StandardCharsets.UTF_8
3Always use try-with-resources for file streams
4Files.readString() for small files; Files.lines() for large files
5Use atomic move (write to temp + move) for safe file replacement
6Files.walk() for recursive directory traversal

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages