Files and I/O with NIO.2

Java · Advanced · 12 min

Almost every application reads configuration, writes reports or processes data files.

Modern file handling uses NIO.2 (java.nio.file). A Path represents a location: Path.of("data", "input.csv") builds it portably, and resolve, getParent, getFileName and normalize manipulate it without touching the disk.

The Files utility class does the actual I/O:

  • Small files: Files.readString(path), Files.readAllLines(path), Files.writeString(path, text) and Files.write(path, lines).
  • Large files: Files.lines(path) returns a lazy Stream<String>; Files.newBufferedReader and newBufferedWriter give classic readers and writers.
  • Management: exists, createDirectories, copy, move (with options like REPLACE_EXISTING, ATOMIC_MOVE), delete, deleteIfExists, size.
  • Directories: Files.list(dir) for one level, Files.walk(dir) recursively, Files.find with a filter.

Streams returned by lines, list and walk hold open file handles, so always use them in try-with-resources. Forgetting this leaks descriptors and eventually fails with "Too many open files".

Text needs a charset. Since Java 18 the default is UTF-8 everywhere (JEP 400), and the Files methods use UTF-8 unless told otherwise. Pass a charset explicitly when reading files from other systems, for example StandardCharsets.ISO_8859_1.

Write options control behaviour: StandardOpenOption.APPEND, CREATE, TRUNCATE_EXISTING. To avoid readers seeing half-written files, write to a temporary file in the same directory and move it into place with ATOMIC_MOVE.

You'll still meet java.io.File and InputStream/OutputStream in older APIs; file.toPath() and path.toFile() bridge the two worlds.

Example

import java.io.IOException;
import java.nio.file.*;

public class FilesDemo {
    public static void main(String[] args) throws IOException {
        Path dir = Files.createTempDirectory("demo");
        Path log = dir.resolve("app.log");
        Files.writeString(log, """
                INFO started
                ERROR disk full
                INFO retry
                ERROR timeout
                """);
        try (var lines = Files.lines(log)) {           // closes the file
            System.out.println(lines.filter(l -> l.startsWith("ERROR")).count()); // 2
        }
        Path tmp = dir.resolve("report.tmp");                     // write, then move into place
        Files.writeString(tmp, "errors=2\n");
        Files.move(tmp, dir.resolve("report.txt"), StandardCopyOption.ATOMIC_MOVE);

        try (var files = Files.list(dir)) {
            files.map(p -> p.getFileName().toString()).sorted().forEach(System.out::println);
        } // app.log, report.txt
    }
}

Remember

  • Use Path.of and the Files utility methods for file I/O
  • Close Files.lines, list and walk streams with try-with-resources
  • UTF-8 is the default since Java 18; specify charsets for foreign files

Watch out for

  • Using Files.readAllLines on multi-gigabyte files and running out of memory — stream them instead.

Check yourself

  1. Why should `Files.lines(path)` be used in try-with-resources?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.oracle.com/javase/tutorial/essential/io/fileio.html

Builds on

Unlocks

Nothing builds on this yet.