14.3 NIO.2 Architecture: `Path`, `Paths`, and Basic `Files` Operations

Key Takeaways

  • java.nio.file.Path is an immutable, hierarchical representation of a system path created via Path.of() (Java 11+) or Paths.get(); it does not require the physical file to exist on disk.

  • Path component methods (getNameCount, getName, subpath) operate strictly on name elements, excluding the root component; subpath(start, end) uses 0-based indexing and endpoint-exclusive bounds.

  • Path manipulation rules: resolve() joins paths (returning the parameter if absolute), while relativize() calculates the relative path between two paths that must both be relative or both be absolute.

  • normalize() eliminates redundant '.' and '..' components purely syntactically without disk access, whereas toRealPath() resolves symbolic links, normalizes, and throws IOException if the target does not exist.

  • Files utility methods provide file manipulation: createDirectories() creates missing parent folders and tolerates existing folders, whereas createDirectory() throws an exception if parents are missing or the target already exists.

Last updated: October 2026

14.3 NIO.2 Architecture: Path, Paths, and Basic Files Operations

Java 7 introduced NIO.2 (Non-blocking I/O 2) under the java.nio.file package to replace the legacy, limitation-prone java.io.File class. NIO.2 separates the path representation from file system operations: the Path interface models locations within a hierarchical file system, while the Files utility class provides dozens of static methods for file creation, copying, moving, deletion, and attribute inspection. On the 1Z0-829 exam, mastering path resolution, relativization, normalization, and file manipulation semantics is essential.


Creating Path Instances

A Path object is an immutable representation of a file or directory path. Crucially, creating a Path does not verify or require that the physical file or directory actually exists on disk.

Java provides several mechanisms to instantiate Path objects:

import java.nio.file.*;
import java.net.URI;

// Approach 1: Modern Path.of() factory (Java 11+ recommended)
Path p1 = Path.of("/home/user/reports");
Path p2 = Path.of("home", "user", "reports", "annual.pdf");
Path p3 = Path.of(URI.create("file:///home/user/reports"));

// Approach 2: Legacy Paths.get() factory (Java 7+)
Path p4 = Paths.get("/home/user/reports");
Path p5 = Paths.get("home", "user", "reports", "annual.pdf");

// Approach 3: FileSystem abstraction
Path p6 = FileSystems.getDefault().getPath("/home/user/reports");

// Interoperability with legacy java.io.File
java.io.File oldFile = p1.toFile();
Path convertedPath = oldFile.toPath();

Path Inspection Methods

A path consists of an optional root component (such as / on Unix or C:\ on Windows) followed by a sequence of name elements separated by path delimiters.

Consider the path: Path p = Path.of("/home/developer/workspace/app.java");

  • p.getRoot(): Returns the root component (/), or null if p is a relative path.
  • p.getNameCount(): Returns the number of name elements. The root component is NOT counted! Here, p.getNameCount() returns 4 (home, developer, workspace, app.java).
  • p.getName(int index): Returns the name element at the 0-based index as a new Path object.
    • p.getName(0) returns home.
    • p.getName(3) returns app.java.
    • Throws IllegalArgumentException if index is < 0 or >= getNameCount().
  • p.getFileName(): Returns the farthest element (last component) as a Path (app.java).
  • p.getParent(): Returns the parent path (/home/developer/workspace). For a top-level relative path like Path.of("app.java"), getParent() returns null!
  • p.isAbsolute(): Returns true if the path contains a root component.
  • p.subpath(int beginIndex, int endIndex): Returns a relative Path representing a sub-sequence of name elements from beginIndex (inclusive) to endIndex (exclusive).
    • p.subpath(1, 3) returns developer/workspace.
    • The root is not part of subpath. The resulting path is always relative.
    • If beginIndex < 0, endIndex > getNameCount(), or beginIndex >= endIndex, it throws IllegalArgumentException.
Path path = Path.of("/var/log/nginx/access.log");
System.out.println(path.getNameCount()); // 4
System.out.println(path.getName(1));      // log
System.out.println(path.subpath(0, 2));   // var/log (relative, no leading slash!)
System.out.println(path.getRoot());       // /

Path Operations: resolve, relativize, normalize, and toRealPath

Path manipulation methods produce many classic trick questions.

1. Path Resolution: resolve(Path other)

resolve joins two paths. It behaves like concatenation:

  • If other is a relative path, resolve joins other onto this.
  • The Absolute Parameter Trap: If other is an absolute path, resolve completely discards this and returns other unchanged!
Path base = Path.of("/app/config");
Path relative = Path.of("db/schema.sql");
Path absolute = Path.of("/var/log/syslog");

System.out.println(base.resolve(relative)); // /app/config/db/schema.sql
System.out.println(base.resolve(absolute)); // /var/log/syslog (discards base!)

resolveSibling(Path other) resolves other against the parent of this: Path.of("/app/config").resolveSibling("data") yields /app/data.

2. Relative Paths: relativize(Path other)

relativize computes the relative path required to navigate from this path to other path:

Path p1 = Path.of("/data/reports/2026");
Path p2 = Path.of("/data/logs/audit");
Path relative = p1.relativize(p2);
System.out.println(relative); // ../../logs/audit

Warning

The relativize() Type Mismatch Trap Both paths passed to relativize() must be of the same type: either both absolute or both relative! Attempting to relativize an absolute path against a relative path (or vice versa) throws java.lang.IllegalArgumentException at runtime. Additionally, on Windows, attempting to relativize paths with different drive letters (C:\ vs D:\) throws IllegalArgumentException.

3. Normalization: normalize()

normalize() eliminates redundant path elements syntactically without querying the file system:

  • . (current directory) elements are removed.
  • .. (parent directory) elements are resolved against preceding path elements.
Path p = Path.of("/a/b/./c/../../d");
System.out.println(p.normalize()); // /a/d

normalize() does not verify if the directories exist or whether symbolic links alter the hierarchy.

4. Real Paths: toRealPath(LinkOption... options)

toRealPath() bridges the gap between syntactic paths and the physical filesystem:

  • Resolves all symbolic links to their real targets (unless LinkOption.NOFOLLOW_LINKS is specified).
  • Performs full normalization (removes redundant . and ..).
  • Converts relative paths to absolute paths.
  • Crucial Requirement: toRealPath() queries the real file system and throws java.io.IOException (specifically NoSuchFileException) if the physical file or directory does not exist!

Core Files Operations

The java.nio.file.Files class contains static utility methods that perform operations directly on the file system.

Checking Existence and Identity

  • Files.exists(Path, LinkOption...): Returns true if the file exists.
  • Files.notExists(Path, LinkOption...): Returns true if the file does not exist.
  • Files.isSameFile(Path p1, Path p2): Checks if two paths locate the same physical file on disk.
    • If p1.equals(p2) is true, it immediately returns true without checking the filesystem.
    • Otherwise, it accesses the disk to resolve symbolic links or alternate path names pointing to the same file inode.
    • If either file does not exist on disk, it throws NoSuchFileException / IOException (unless the paths are equal).

Creating Files and Directories

  • Files.createFile(Path): Creates a new, empty file. If the file already exists, it throws FileAlreadyExistsException. If intermediate parent directories are missing, it throws NoSuchFileException.
  • Files.createDirectory(Path): Creates a single directory. If the target directory already exists, it throws FileAlreadyExistsException. If any parent directories do not exist, it throws NoSuchFileException.
  • Files.createDirectories(Path): Creates the target directory along with all non-existent parent directories. Does not throw an exception if the directory already exists!
Path singleDir = Path.of("/data/inbox");
Files.createDirectory(singleDir); // Fails if /data is missing or /data/inbox exists

Path deepDirs = Path.of("/data/2026/q3/invoices");
Files.createDirectories(deepDirs); // Safely creates all missing parents; no-op if exists

Copying and Moving Files

  • Files.copy(Path source, Path target, CopyOption... options):
    • By default, throws FileAlreadyExistsException if target exists.
    • Use StandardCopyOption.REPLACE_EXISTING to overwrite the destination.
    • If source is a directory, copy() performs a shallow copy: it creates the target directory, but does not copy files or subdirectories inside it!
    • StandardCopyOption.COPY_ATTRIBUTES: Preserves file attributes (such as timestamps).
  • Files.move(Path source, Path target, CopyOption... options):
    • Moves or renames a file or directory.
    • StandardCopyOption.ATOMIC_MOVE: Executes the move as an atomic file system operation (supported only if source and target reside on the same volume).
    • If target exists, it fails unless StandardCopyOption.REPLACE_EXISTING is supplied.

Deleting Files

  • Files.delete(Path): Deletes the file or directory. Throws NoSuchFileException if the target does not exist. Throws DirectoryNotEmptyException if the target is a non-empty directory.
  • Files.deleteIfExists(Path): Returns true if the file was deleted, or false if the file did not exist. Still throws DirectoryNotEmptyException if deleting a non-empty directory.

Reading and Writing File Content with Files

The objective asks you to read and write files through java.nio.file, not only to manipulate Path objects. These Files methods use UTF-8 unless you pass a Charset:

MethodWhat it doesNotes
Files.readAllLines(path)Returns List<String>Loads the whole file into memory
Files.readString(path) (Java 11)Returns the whole file as one StringAlso loads everything
Files.readAllBytes(path)Returns byte[]Binary content
Files.writeString(path, text, options...) (Java 11)Writes a CharSequenceDefault options: CREATE, TRUNCATE_EXISTING, WRITE
Files.write(path, lines, options...)Writes each element of an Iterable<? extends CharSequence> followed by a line separatorFiles.write(path, List.of("a", "b")) writes a, newline, b, newline
Files.newBufferedReader(path) / Files.newBufferedWriter(path, options...)Return a BufferedReader / BufferedWriterClose them with try-with-resources
Files.newInputStream(path) / Files.newOutputStream(path)Return byte streamsBridge to classic java.io

StandardOpenOption values change the behavior:

  • APPEND adds to the end of an existing file. Used alone, it throws NoSuchFileException if the file does not exist; combine it with CREATE to create the file when absent.
  • CREATE_NEW fails with FileAlreadyExistsException if the file already exists.
  • TRUNCATE_EXISTING empties an existing file before writing (part of the default for writeString and write).
Path log = Path.of("app.log");
Files.writeString(log, "one\n");                              // creates or truncates the file
Files.writeString(log, "two\n", StandardOpenOption.APPEND);   // appends to the existing file
System.out.println(Files.readAllLines(log));                 // [one, two]

try (BufferedWriter w = Files.newBufferedWriter(log)) {        // truncates by default
    w.write("buffered");
    w.newLine();
}
try (BufferedReader r = Files.newBufferedReader(log)) {
    System.out.println(r.readLine());                          // buffered
}

Useful property queries include Files.size(path), Files.isDirectory(path), Files.isRegularFile(path), Files.isReadable(path), Files.getLastModifiedTime(path), and Files.mismatch(p1, p2) (Java 12), which returns -1 when two files have identical content.

Test Your Knowledge

Consider the following code snippet:

import java.nio.file.Path;

public class PathResolution {
    public static void main(String[] args) {
        Path p1 = Path.of("/usr/local/bin");
        Path p2 = Path.of("/etc/hosts");
        Path p3 = p1.resolve(p2);
        System.out.println(p3);
    }
}

What is printed when this program runs?

A

Throws an IllegalArgumentException at runtime

B

/usr/local/bin/hosts

C

/etc/hosts

D

/usr/local/bin/etc/hosts

Test Your Knowledge

Consider the following code attempting to construct a relative path:

import java.nio.file.Path;

public class PathRelativize {
    public static void main(String[] args) {
        Path path1 = Path.of("/home/reports");
        Path path2 = Path.of("2026/finance.xlsx");
        Path result = path1.relativize(path2);
        System.out.println(result);
    }
}

What is the result of attempting to compile and run this code?

A

Compiles and prints: /home/reports/2026/finance.xlsx, the joined path

B

Compiles and prints: ../../2026/finance.xlsx after normalizing

C

Compiles and prints: null, because the paths share no root

D

Throws IllegalArgumentException: one path is absolute, one relative

Test Your Knowledge

Consider the following code:

import java.nio.file.Path;

public class PathSubpath {
    public static void main(String[] args) {
        Path path = Path.of("/zoo/mammals/bear/polar.txt");
        System.out.println(path.subpath(1, 3));
    }
}

What is printed when this program runs?

A

/mammals/bear/polar.txt

B

zoo/mammals/bear

C

zoo/mammals

D

mammals/bear

Test Your Knowledge

Which statement correctly describes the behavior of Files.createDirectories(Path dir)?

A

It requires all parent directories to already exist and throws NoSuchFileException if any parent is missing.

B

It creates all non-existent parent directories as needed and completes normally without throwing an exception if the target directory already exists.

C

It deletes any existing directory before creating a fresh, empty directory structure.

D

It throws a DirectoryNotEmptyException if the target path is an existing non-empty directory.

Test Your Knowledge

Assume report.txt does not exist and the method declares throws IOException. What is the result?

Path p = Path.of("report.txt");
Files.writeString(p, "A\n");
Files.writeString(p, "B\n", StandardOpenOption.APPEND);
Files.writeString(p, "C\n", StandardOpenOption.CREATE_NEW);
System.out.println(Files.readAllLines(p));
A

A FileAlreadyExistsException is thrown by the CREATE_NEW call, so nothing is printed.

B

[A, B, C] is printed.

C

A NoSuchFileException is thrown by the APPEND call.

D

[C] is printed, because CREATE_NEW replaces the existing file.

Sections you finish are checked off in the contents.