Skip to content
Java advanced 3 min read

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: File methods like delete() and mkdir() swallow the cause of failure, returning only false. 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: readString and readAllLines load 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.

TaskBest API
Small file → StringFiles.readString
Small file → linesFiles.readAllLines
Large file, lazyFiles.lines (in try-with-resources)
Streaming line-by-lineFiles.newBufferedReader
Write small contentFiles.writeString / Files.write
Streaming writeFiles.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 legacy java.io.File in new code.
  • Wrap every stream-returning method (Files.lines, buffered readers/writers) in try-with-resources.
  • Use Files.lines for large files to avoid loading everything into memory.
  • Be explicit about character encoding to ensure cross-platform correctness.
  • Build paths with Path.of/resolve rather than string concatenation with separators.
  • Handle IOException meaningfully — log the failing path and cause, don’t silently ignore.
Last updated June 1, 2026
Was this helpful?