Skip to content
Java file handling 7 min read

NIO.2: Path & Files

Java 7 introduced NIO.2 (java.nio.file), a complete overhaul of file I/O that fixes the long-standing pain points of the old java.io.File class. If you are writing new code today, Path and Files should be your first choice for anything file-system related.

Tip: Already using java.io.File? You can bridge the two worlds with file.toPath() and path.toFile(), so you can adopt NIO.2 incrementally.

Why NIO.2 Instead of java.io.File?

The classic File class works, but it has frustrating design quirks:

Pain point with java.io.FileNIO.2 solution
delete(), mkdir() return boolean — no error detailMethods throw IOException with a real message
No symbolic link supportFull symlink awareness
No file attributes (owner, POSIX permissions, etc.)Rich attribute views
No directory tree walking APIFiles.walk(), Files.walkFileTree()
renameTo() is unreliable across file systemsFiles.move() is atomic on the same FS
Slow directory listingFiles.newDirectoryStream() is lazy

The Three Key Classes

Everything in NIO.2 revolves around three classes in java.nio.file:

  • Path — an object that represents a file or directory path (immutable, like String).
  • Paths — a factory class with static methods to create Path objects. (Java 11+ can use Path.of() directly.)
  • Files — a utility class with static methods that actually do things: copy, move, read, write, walk, check attributes, etc.

Creating a Path

import java.nio.file.Path;
import java.nio.file.Paths;

public class PathDemo {
    public static void main(String[] args) {
        // Java 7–10: use Paths.get()
        Path p1 = Paths.get("data/config.txt");

        // Java 11+: use Path.of() — cleaner
        Path p2 = Path.of("data", "config.txt");  // varargs segments

        // Absolute path
        Path abs = Path.of("/home/user/docs/report.pdf");

        System.out.println(p1);           // data/config.txt
        System.out.println(p2);           // data/config.txt
        System.out.println(abs.getFileName()); // report.pdf
        System.out.println(abs.getParent());   // /home/user/docs
        System.out.println(abs.getRoot());     // /
    }
}

Output:

data/config.txt
data/config.txt
report.pdf
/home/user/docs
/

Useful Path Methods

Path itself is mostly about navigating and inspecting the path — it does not touch the file system.

Path p = Path.of("/home/user/projects/app/src/Main.java");

System.out.println(p.getNameCount());   // 6  (number of elements)
System.out.println(p.getName(0));       // home
System.out.println(p.getName(5));       // Main.java
System.out.println(p.subpath(3, 5));    // app/src
System.out.println(p.isAbsolute());     // true
System.out.println(p.normalize());      // cleans up any .. or . segments
System.out.println(p.resolve("test")); // /home/user/projects/app/src/Main.java/test

resolve() appends a relative path, and relativize() computes the relative path between two paths:

Path base = Path.of("/home/user");
Path target = Path.of("/home/user/docs/report.pdf");

System.out.println(base.relativize(target)); // docs/report.pdf

Checking Files with Files

Files provides all the I/O operations. These methods talk to the file system and throw IOException when something goes wrong.

import java.nio.file.Files;
import java.nio.file.Path;

public class FilesCheck {
    public static void main(String[] args) throws Exception {
        Path p = Path.of("data/config.txt");

        System.out.println(Files.exists(p));          // true/false
        System.out.println(Files.isDirectory(p));     // false
        System.out.println(Files.isRegularFile(p));   // true
        System.out.println(Files.isReadable(p));      // true
        System.out.println(Files.isWritable(p));      // true
        System.out.println(Files.isHidden(p));        // false
        System.out.println(Files.size(p));            // file size in bytes
    }
}

Reading and Writing Files

NIO.2 makes common read/write tasks one-liners.

Read All Lines into a List

import java.nio.file.*;
import java.util.List;

public class ReadLines {
    public static void main(String[] args) throws Exception {
        List<String> lines = Files.readAllLines(Path.of("notes.txt"));
        lines.forEach(System.out::println);
    }
}

Read All Bytes

byte[] bytes = Files.readAllBytes(Path.of("image.png"));
System.out.println("Bytes read: " + bytes.length);

Write Lines to a File

import java.nio.file.*;
import java.util.List;

public class WriteLines {
    public static void main(String[] args) throws Exception {
        List<String> content = List.of("Line 1", "Line 2", "Line 3");

        // Creates the file if it does not exist; overwrites if it does
        Files.write(Path.of("output.txt"), content);

        // Append instead of overwrite
        Files.write(Path.of("output.txt"), List.of("Line 4"),
                    StandardOpenOption.APPEND);
    }
}

Tip: Files.writeString() (Java 11+) is even more convenient when you have a single String to write.

Files.writeString(Path.of("hello.txt"), "Hello, NIO.2!");
String text = Files.readString(Path.of("hello.txt")); // Java 11+
System.out.println(text); // Hello, NIO.2!

Creating, Copying, Moving, and Deleting

Create Files and Directories

import java.nio.file.*;

public class CreateDemo {
    public static void main(String[] args) throws Exception {
        // Create a single directory
        Files.createDirectory(Path.of("output"));

        // Create full path (like mkdirs)
        Files.createDirectories(Path.of("output/reports/2024"));

        // Create an empty file
        Files.createFile(Path.of("output/report.txt"));
    }
}

Warning: Files.createDirectory() throws FileAlreadyExistsException if the directory already exists. Use Files.createDirectories() when you are not sure.

Copy a File

import java.nio.file.*;

public class CopyDemo {
    public static void main(String[] args) throws Exception {
        Path src = Path.of("original.txt");
        Path dst = Path.of("backup/original_copy.txt");

        // REPLACE_EXISTING overwrites the destination if it exists
        Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("Copied successfully");
    }
}

Move (Rename) a File

Files.move(
    Path.of("draft.txt"),
    Path.of("archive/final.txt"),
    StandardCopyOption.REPLACE_EXISTING,
    StandardCopyOption.ATOMIC_MOVE   // best-effort atomic; OS-dependent
);

ATOMIC_MOVE asks the OS to perform the move atomically so another process never sees a partial file. It falls back silently if the OS does not support it for that path.

Delete a File

// Throws NoSuchFileException if it does not exist
Files.delete(Path.of("temp.log"));

// Silent version — returns false if not found, never throws
Files.deleteIfExists(Path.of("temp.log"));

Walking a Directory Tree

Files.walk() — Stream-based Walking

import java.nio.file.*;
import java.util.stream.Stream;

public class WalkDemo {
    public static void main(String[] args) throws Exception {
        try (Stream<Path> stream = Files.walk(Path.of("src"))) {
            stream
                .filter(Files::isRegularFile)
                .filter(p -> p.toString().endsWith(".java"))
                .forEach(System.out::println);
        }
    }
}

Output (example):

src/com/example/Main.java
src/com/example/Utils.java

Note: Always use Files.walk() inside a try-with-resources block — it returns a lazy Stream backed by an open directory iterator that must be closed.

Delete a Non-Empty Directory

The old File.delete() fails on non-empty directories. With NIO.2 you walk the tree and delete bottom-up:

import java.nio.file.*;
import java.util.Comparator;
import java.util.stream.Stream;

public class DeleteDir {
    public static void main(String[] args) throws Exception {
        Path dir = Path.of("build");
        try (Stream<Path> walk = Files.walk(dir)) {
            walk.sorted(Comparator.reverseOrder()) // deepest files first
                .forEach(p -> {
                    try { Files.delete(p); }
                    catch (Exception e) { throw new RuntimeException(e); }
                });
        }
        System.out.println("Deleted: " + !Files.exists(dir));
    }
}

File Attributes

NIO.2 gives you access to rich metadata beyond what java.io.File exposes.

import java.nio.file.*;
import java.nio.file.attribute.*;

public class AttributesDemo {
    public static void main(String[] args) throws Exception {
        Path p = Path.of("data.txt");

        BasicFileAttributes attrs =
            Files.readAttributes(p, BasicFileAttributes.class);

        System.out.println("Size:          " + attrs.size());
        System.out.println("Created:       " + attrs.creationTime());
        System.out.println("Last modified: " + attrs.lastModifiedTime());
        System.out.println("Is symlink:    " + attrs.isSymbolicLink());
        System.out.println("Is directory:  " + attrs.isDirectory());
    }
}

On POSIX systems (Linux, macOS) you can read POSIX permissions:

PosixFileAttributes posix =
    Files.readAttributes(p, PosixFileAttributes.class);
System.out.println(PosixFilePermissions.toString(posix.permissions()));
// e.g.  rw-r--r--

Watching a Directory for Changes

WatchService (also part of NIO.2) lets you monitor a directory for file-system events without polling.

import java.nio.file.*;

public class WatchDemo {
    public static void main(String[] args) throws Exception {
        WatchService watcher = FileSystems.getDefault().newWatchService();
        Path dir = Path.of("watched");

        dir.register(watcher,
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_MODIFY,
            StandardWatchEventKinds.ENTRY_DELETE);

        System.out.println("Watching " + dir + " ...");
        WatchKey key = watcher.take(); // blocks until an event occurs
        for (WatchEvent<?> event : key.pollEvents()) {
            System.out.println(event.kind() + ": " + event.context());
        }
        key.reset();
    }
}

Under the Hood

Path Is an Interface

Path is actually an interface in java.nio.file. When you call Path.of() or Paths.get(), you get a platform-specific implementation: sun.nio.fs.UnixPath on Linux/macOS, or sun.nio.fs.WindowsPath on Windows. This is why path comparison with equals() is case-sensitive on Linux but case-insensitive on Windows — the implementation delegates to the OS.

Files and the Provider SPI

Files is a facade over a FileSystemProvider (a Service Provider Interface). The default provider maps to the native OS file system. Third-party providers (ZIP file systems, cloud file systems, in-memory file systems for testing) can be plugged in transparently — your Files.copy() call works the same regardless of the underlying provider.

Atomic Operations and OS Guarantees

Files.move() with ATOMIC_MOVE maps to rename(2) on Linux, which is atomic within the same mount point. Files.createTempFile() uses O_EXCL internally, so there is no race condition between checking existence and creating the file — unlike File.createNewFile() on some systems.

Memory-Efficient Directory Streaming

Files.newDirectoryStream() wraps the OS readdir() call and yields entries one at a time, so it does not load the whole directory listing into memory. This is far more efficient than file.listFiles() in very large directories (millions of entries).

try (DirectoryStream<Path> stream = Files.newDirectoryStream(Path.of("."))) {
    for (Path entry : stream) {
        System.out.println(entry.getFileName());
    }
}

Quick Reference: Files Method Cheat Sheet

MethodSinceDescription
Files.exists(path)7Check if path exists
Files.readAllLines(path)7Read lines into List<String>
Files.readAllBytes(path)7Read raw bytes
Files.readString(path)11Read entire file as String
Files.write(path, lines)7Write lines to file
Files.writeString(path, s)11Write a String to file
Files.copy(src, dst)7Copy file
Files.move(src, dst)7Move/rename file
Files.delete(path)7Delete (throws if missing)
Files.deleteIfExists(path)7Delete silently
Files.createDirectories(path)7Create full directory tree
Files.walk(path)7Lazy recursive stream of all paths
Files.lines(path)8Lazy Stream<String> of lines
Files.readAttributes(path, cls)7Read file metadata
  • File Class — the classic java.io.File API and when you might still reach for it
  • File Handling — overview of all file I/O approaches in Java
  • Read a File Line by Line — comparing BufferedReader vs Files.readAllLines() vs Files.lines()
  • Write to a File — all the ways to write text and binary data to disk
  • Delete a File — deleting files and directories safely
  • Serialization — persisting Java objects to a file with ObjectOutputStream
Last updated June 13, 2026
Was this helpful?