11.2 Java NIO.2 File API: Path, Paths, and File System Operations

Key Takeaways

  • Path represents an immutable, hierarchical file system location instantiated via Path.of(...) in modern Java, operating purely on lexical representations in memory without requiring disk access.
  • Lexical path methods such as normalize(), relativize(), resolve(), and subpath() manipulate path structures syntactically, while toRealPath() accesses the physical file system to verify existence and resolve symlinks.
  • The relativize() method requires both paths to be either absolute or relative, throwing IllegalArgumentException if types are mixed or if paths on Windows reside on different root drives.
  • The subpath(begin, end) method uses 0-based element indexing, excludes the root component, and always constructs a relative Path.
  • The Files utility class provides atomic and attribute-aware operations (copy, move, deleteIfExists, readString, writeString) where directory copy is strictly shallow by default.
Last updated: September 2026

Java NIO.2 File API: Path, Paths, and File System Operations

Java 7 introduced NIO.2 (New I/O 2) under the java.nio.file package, completely modernizing file system interactions and overcoming severe architectural limitations of the legacy java.io.File class. NIO.2 introduces rich, type-safe abstractions for path manipulation, cross-platform file attribute inspection, symbolic link resolution, atomic operations, and extensible file system providers (including ZIP file systems and in-memory virtual file systems).

For the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam, developers must master the immutable java.nio.file.Path interface, distinguish strictly between in-memory lexical operations and physical disk-based operations, and understand the precise behavior of static helper methods in java.nio.file.Files.


1. The Path Interface and Factory Methods

A Path represents a hierarchical, platform-specific location of a file or directory within a file system. Path objects are immutable, comparable, and safe to share across concurrent execution threads.

Creating Path Instances

Java SE 21 supports several approaches to instantiate Path objects:

// 1. Recommended Modern Approach (Java 11+): Path.of()
Path p1 = Path.of("data", "reports", "annual.csv");
Path p2 = Path.of("/var/log/application.log");
Path p3 = Path.of(URI.create("file:///home/developer/config.json"));

// 2. Classic NIO.2 Factory (Java 7+): Paths.get()
Path p4 = Paths.get("data", "reports", "annual.csv");
Path p5 = Paths.get(URI.create("file:///opt/app/server.xml"));

// 3. FileSystems Factory:
Path p6 = FileSystems.getDefault().getPath("data", "reports", "annual.csv");

// 4. Interoperability with Legacy java.io.File:
File legacyFile = p1.toFile();
Path modernPath = legacyFile.toPath();

[!NOTE] Path.of(...) (introduced in Java 11) is a direct syntactic shortcut for Paths.get(...). Both methods delegate internally to FileSystems.getDefault().getPath(...). On modern Java exams, Path.of(...) is the preferred idiom.


2. Lexical Path Inspection and Slicing (In-Memory Only)

Lexical path operations inspect and manipulate the path's text structure in JVM memory without interacting with the physical operating system file system. The targeted files or directories do not need to exist on disk for lexical operations to execute successfully.

Path path = Path.of("/projects/2026/finance/summary.txt");

System.out.println(path.toString());       // "/projects/2026/finance/summary.txt"
System.out.println(path.getFileName());    // "summary.txt" (returned as a Path instance)
System.out.println(path.getParent());      // "/projects/2026/finance" (Path instance)
System.out.println(path.getRoot());        // "/" on Unix; "C:\" on Windows; null if relative!
System.out.println(path.isAbsolute());     // true

// Name element indexing: 0-indexed, EXCLUDES ROOT!
System.out.println(path.getNameCount());   // 4 (projects, 2026, finance, summary.txt)
System.out.println(path.getName(0));       // "projects" (Path instance)
System.out.println(path.getName(2));       // "finance" (Path instance)

The subpath(int beginIndex, int endIndex) Method

The subpath() method slices a portion of a path's name element sequence:

  • Indexing: Uses 0-based indexing over the path's name elements.
  • Parameters: beginIndex is inclusive; endIndex is exclusive.
  • Relative Path Rule: subpath() never includes the root component (/ or C:\). The resulting Path is always a relative path, even if sliced from an absolute path!
  • Exception Rule: Throws java.lang.IllegalArgumentException at runtime if:
    • beginIndex < 0
    • beginIndex >= path.getNameCount()
    • endIndex <= beginIndex
    • endIndex > path.getNameCount()
Path p = Path.of("/home/developer/workspace/app/Main.java");
// Name elements: [0] home, [1] developer, [2] workspace, [3] app, [4] Main.java
// Total getNameCount() == 5

Path sub1 = p.subpath(1, 4);
System.out.println(sub1);               // "developer/workspace/app" (Relative path!)
System.out.println(sub1.isAbsolute());  // false

// Slicing single element:
Path sub2 = p.subpath(0, 1);
System.out.println(sub2);               // "home" (Relative path, no leading slash!)

Element Matching: startsWith() and endsWith()

The startsWith(Path/String) and endsWith(Path/String) methods in NIO.2 match complete path name elements, not arbitrary string character prefixes/suffixes:

Path path = Path.of("/data/reports/2026.csv");

// Matching complete name elements:
System.out.println(path.startsWith("/data"));          // true
System.out.println(path.startsWith("/data/reports"));  // true
System.out.println(path.endsWith("2026.csv"));         // true
System.out.println(path.endsWith(Path.of("reports/2026.csv"))); // true

// TRAP: Substring matching fails!
System.out.println(path.startsWith("/dat"));           // false (not a complete element)
System.out.println(path.endsWith(".csv"));             // false (not a complete element)

3. Lexical Transformations: normalize(), resolve(), and relativize()

normalize()

Eliminates redundant name elements such as . (current directory) and .. (parent directory navigation) by analyzing the string hierarchy:

Path dirty = Path.of("/var/log/../log/./app/debug.log");
Path clean = dirty.normalize();
System.out.println(clean); // "/var/log/app/debug.log"

Path relativeDirty = Path.of("a/b/../../c");
System.out.println(relativeDirty.normalize()); // "c"

resolve() and resolveSibling()

The resolve() method joins two paths together (hierarchical path concatenation):

  • path.resolve(Path other) / path.resolve(String other):
    • If other is a relative path, resolve() appends other to path.
    • If other is an absolute path, resolve() discards path and returns other unchanged!
    • If other is empty (Path.of("")), resolve() returns path.
  • path.resolveSibling(Path other):
    • Resolves other against path.getParent(), effectively replacing the file name element.
Path base = Path.of("/opt/app");
Path rel = Path.of("config/server.xml");
Path abs = Path.of("/etc/hosts");

System.out.println(base.resolve(rel));        // "/opt/app/config/server.xml"
System.out.println(base.resolve(abs));        // "/etc/hosts" (Absolute argument wins!)

Path file = Path.of("/opt/app/application.log");
System.out.println(file.resolveSibling("config.json")); // "/opt/app/config.json"

relativize() (Navigational Step Construction)

The relativize() method calculates the relative navigational path required to travel from this path to other path:

Path p1 = Path.of("/usr/local/bin");
Path p2 = Path.of("/usr/share/doc");

Path relative = p1.relativize(p2);
System.out.println(relative); // "../../share/doc"

Golden Exam Traps for relativize()

[!WARNING]

  1. Mixed Path Types: Attempting to relativize between an absolute path and a relative path throws java.lang.IllegalArgumentException at runtime! Both paths must be absolute, or both paths must be relative.
  2. Different Roots (Windows): On Windows file systems, attempting to relativize across different drive root letters (e.g., C:\projects and D:\data) throws java.lang.IllegalArgumentException because no relative path can navigate across independent root partitions.
Path absPath = Path.of("/usr/bin");
Path relPath = Path.of("data/file.txt");

// RUNTIME ERROR: Throws IllegalArgumentException!
absPath.relativize(relPath);

4. Physical Path Resolution: toAbsolutePath() vs. toRealPath()

NIO.2 provides two distinct methods to convert paths to absolute forms:

FeaturetoAbsolutePath()toRealPath(LinkOption... options)
File System AccessLexical only (No disk I/O)Physical (Queries operating system)
Disk Existence Required?No (File does not need to exist)Yes (Throws NoSuchFileException if missing)
Symbolic Link ResolutionKeeps symbolic links unresolvedResolves symbolic links to target files
NormalizationRetains . and .. componentsAutomatically normalizes redundant components
Checked ExceptionsNone (toAbsolutePath() never throws)Throws IOException (NoSuchFileException)
Path relPath = Path.of("config.json");

// Purely lexical: prepends current working directory
Path abs = relPath.toAbsolutePath(); 
System.out.println(abs); // "/home/developer/workspace/config.json" (No disk verification!)

// Physical verification:
try {
    Path real = relPath.toRealPath(LinkOption.NOFOLLOW_LINKS);
    System.out.println("Canonical physical path: " + real);
} catch (NoSuchFileException e) {
    System.err.println("File does not exist on disk: " + e.getFile());
}

5. File Operations with the java.nio.file.Files Utility Class

The Files class contains static helper methods that operate directly on the underlying file system.

Verification and Attribute Inspection

Path p = Path.of("/data/report.pdf");

// Existence checks:
boolean exists = Files.exists(p, LinkOption.NOFOLLOW_LINKS);
boolean notExists = Files.notExists(p);

// File type queries:
boolean isFile = Files.isRegularFile(p);
boolean isDir = Files.isDirectory(p);
boolean isSym = Files.isSymbolicLink(p);
boolean isReadable = Files.isReadable(p);
boolean isWritable = Files.isWritable(p);
boolean isExecutable = Files.isExecutable(p);
boolean isHidden = Files.isHidden(p);
long sizeInBytes = Files.size(p); // Throws IOException if file is missing

[!IMPORTANT] The Existence Tri-State Trap: In NIO.2, !Files.exists(p) is not always equivalent to Files.notExists(p). If the JVM lacks security permissions to read the parent directory or verify the file's presence, both Files.exists(p) and Files.notExists(p) can return false simultaneously!

Identity Verification: Files.isSameFile()

Files.isSameFile(Path p1, Path p2) determines whether two paths locate the exact same file system entity:

  • If p1.equals(p2) evaluates to true, isSameFile() returns true immediately without accessing the file system.
  • If p1 and p2 are syntactically distinct, isSameFile() accesses the file system to compare unique operating system file identifiers (such as Unix inodes or Windows file index identifiers).
  • If either file does not exist on disk when disk lookup is required, an IOException (NoSuchFileException) is thrown.
Path link = Path.of("/usr/bin/python");
Path target = Path.of("/usr/bin/python3.11");

// Returns true if /usr/bin/python is a symbolic link pointing to /usr/bin/python3.11:
boolean same = Files.isSameFile(link, target);

Creating Files and Directories

MethodTarget BehaviorException Handling
Files.createFile(path)Creates a new empty file.Throws FileAlreadyExistsException if file exists; throws NoSuchFileException if parent is missing.
Files.createDirectory(path)Creates a single directory.Throws FileAlreadyExistsException if target exists; throws NoSuchFileException if parent directory is missing.
Files.createDirectories(path)Creates target and all missing parent directories.Does NOT throw if directory already exists. Creates nested directory trees safely.
Files.createTempFile(prefix, suffix)Creates temporary file in OS temp folder.Generates unique name (e.g. app-91823.tmp).
Files.createTempDirectory(prefix)Creates temporary directory in OS temp folder.Generates unique folder.
// Safely creating nested directory structures:
Path deepDir = Path.of("/opt/app/data/logs/2026");
Files.createDirectories(deepDir); // Creates all missing intermediate directories

Copying and Moving Files

Path src = Path.of("source.txt");
Path dest = Path.of("backup.txt");

// 1. Copying with options:
Files.copy(src, dest, 
    StandardCopyOption.REPLACE_EXISTING, 
    StandardCopyOption.COPY_ATTRIBUTES, 
    LinkOption.NOFOLLOW_LINKS);

// 2. Moving / Renaming:
Files.move(src, dest, 
    StandardCopyOption.REPLACE_EXISTING, 
    StandardCopyOption.ATOMIC_MOVE);

Critical Exam Copy/Move Rules

  1. Shallow Directory Copy Trap: When Files.copy() is invoked on a directory, it performs a shallow copy. It creates the destination directory, but it does NOT copy any of the files or subdirectories contained inside it! To copy a directory tree recursively, developers must walk the tree and copy each entry.
  2. Directory Move Semantics: Moving a non-empty directory on the same file system volume succeeds and moves the directory along with all its children (by updating file system directory pointers). Moving a non-empty directory across different volumes throws DirectoryNotEmptyException unless copied recursively.
  3. ATOMIC_MOVE: The StandardCopyOption.ATOMIC_MOVE option guarantees that the move operation executes as an atomic file system transaction. If the underlying operating system or drive configuration does not support atomic moves (e.g., across separate physical drives), AtomicMoveNotSupportedException is thrown.
  4. Stream Interoperability:
    • Files.copy(InputStream in, Path target, CopyOption... options): Writes all bytes from an input stream to a file.
    • Files.copy(Path source, OutputStream out): Writes all bytes from a file to an output stream.

Deleting Files and Directories

  • Files.delete(Path path): Deletes the target file or directory. Throws NoSuchFileException if the path does not exist. Throws DirectoryNotEmptyException if the target is a non-empty directory.
  • Files.deleteIfExists(Path path): Returns true if the file/directory existed and was deleted; returns false if the path did not exist. Throws DirectoryNotEmptyException if the directory is non-empty.

6. Convenience Helper Methods for Small Files (Java 11+)

For small-to-medium files, NIO.2 provides high-level convenience methods on Files that manage stream lifecycle and buffering automatically:

Path configPath = Path.of("config.json");

// 1. Read entire file into a String (Java 11+):
String jsonContent = Files.readString(configPath, StandardCharsets.UTF_8);

// 2. Write String directly to a file (Java 11+):
Files.writeString(configPath, jsonContent, StandardCharsets.UTF_8, 
    StandardOpenOption.CREATE, 
    StandardOpenOption.WRITE, 
    StandardOpenOption.TRUNCATE_EXISTING);

// 3. Read all lines into a List<String>:
List<String> lines = Files.readAllLines(configPath, StandardCharsets.UTF_8);

// 4. Read / Write raw byte arrays:
byte[] rawBytes = Files.readAllBytes(configPath);
Files.write(configPath, rawBytes, StandardOpenOption.CREATE, StandardOpenOption.WRITE);

StandardOpenOption Enumeration Flags

  • StandardOpenOption.READ: Open for read access.
  • StandardOpenOption.WRITE: Open for write access.
  • StandardOpenOption.CREATE: Create a new file if it does not exist.
  • StandardOpenOption.CREATE_NEW: Create a new file, failing with FileAlreadyExistsException if the file already exists.
  • StandardOpenOption.TRUNCATE_EXISTING: Truncate the file to 0 bytes if it already exists.
  • StandardOpenOption.APPEND: Append new data to the end of the existing file.
Loading diagram...
NIO.2 Path Operations: Lexical vs. Physical Resolution
Test Your Knowledge

Given the following code snippet:

Path path = Path.of("/home/user/workspace/project/src/App.java");
Path sub = path.subpath(1, 4);
System.out.println(sub + " " + sub.isAbsolute());
What is the printed output?

A
B
C
D
Test Your Knowledge

Examine the following code using the Path.resolve() method:

Path base = Path.of("/home/developer/app");
Path relativeTarget = Path.of("config/settings.json");
Path absoluteTarget = Path.of("/etc/security/keys.pem");

Path r1 = base.resolve(relativeTarget);
Path r2 = base.resolve(absoluteTarget);

System.out.println(r1);
System.out.println(r2);
What does this code output?

A
B
C
D
Test Your Knowledge

What happens when executing the following code snippet?

Path p1 = Path.of("/var/data/log");
Path p2 = Path.of("backup/2026");
Path result = p1.relativize(p2);
System.out.println(result);
What is the outcome?

A
B
C
D
Test Your Knowledge

A developer executes the following statement where /source/dataDir is a directory containing several files and subdirectories:

Path source = Path.of("/source/dataDir");
Path destination = Path.of("/backup/dataDir");
Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
Assuming the parent directory /backup already exists and no security exceptions occur, what is the exact state of /backup/dataDir after execution?

A
B
C
D