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.
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 (/), ornullifpis a relative path.p.getNameCount(): Returns the number of name elements. The root component is NOT counted! Here,p.getNameCount()returns4(home,developer,workspace,app.java).p.getName(int index): Returns the name element at the 0-based index as a newPathobject.p.getName(0)returnshome.p.getName(3)returnsapp.java.- Throws
IllegalArgumentExceptionif index is< 0or>= getNameCount().
p.getFileName(): Returns the farthest element (last component) as aPath(app.java).p.getParent(): Returns the parent path (/home/developer/workspace). For a top-level relative path likePath.of("app.java"),getParent()returnsnull!p.isAbsolute(): Returnstrueif the path contains a root component.p.subpath(int beginIndex, int endIndex): Returns a relativePathrepresenting a sub-sequence of name elements frombeginIndex(inclusive) toendIndex(exclusive).p.subpath(1, 3)returnsdeveloper/workspace.- The root is not part of
subpath. The resulting path is always relative. - If
beginIndex < 0,endIndex > getNameCount(), orbeginIndex >= endIndex, it throwsIllegalArgumentException.
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
otheris a relative path,resolvejoinsotherontothis. - The Absolute Parameter Trap: If
otheris an absolute path,resolvecompletely discardsthisand returnsotherunchanged!
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_LINKSis specified). - Performs full normalization (removes redundant
.and..). - Converts relative paths to absolute paths.
- Crucial Requirement:
toRealPath()queries the real file system and throwsjava.io.IOException(specificallyNoSuchFileException) 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...): Returnstrueif the file exists.Files.notExists(Path, LinkOption...): Returnstrueif 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)istrue, it immediately returnstruewithout 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).
- If
Creating Files and Directories
Files.createFile(Path): Creates a new, empty file. If the file already exists, it throwsFileAlreadyExistsException. If intermediate parent directories are missing, it throwsNoSuchFileException.Files.createDirectory(Path): Creates a single directory. If the target directory already exists, it throwsFileAlreadyExistsException. If any parent directories do not exist, it throwsNoSuchFileException.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
FileAlreadyExistsExceptioniftargetexists. - Use
StandardCopyOption.REPLACE_EXISTINGto overwrite the destination. - If
sourceis 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).
- By default, throws
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
targetexists, it fails unlessStandardCopyOption.REPLACE_EXISTINGis supplied.
Deleting Files
Files.delete(Path): Deletes the file or directory. ThrowsNoSuchFileExceptionif the target does not exist. ThrowsDirectoryNotEmptyExceptionif the target is a non-empty directory.Files.deleteIfExists(Path): Returnstrueif the file was deleted, orfalseif the file did not exist. Still throwsDirectoryNotEmptyExceptionif 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:
| Method | What it does | Notes |
|---|---|---|
Files.readAllLines(path) | Returns List<String> | Loads the whole file into memory |
Files.readString(path) (Java 11) | Returns the whole file as one String | Also loads everything |
Files.readAllBytes(path) | Returns byte[] | Binary content |
Files.writeString(path, text, options...) (Java 11) | Writes a CharSequence | Default options: CREATE, TRUNCATE_EXISTING, WRITE |
Files.write(path, lines, options...) | Writes each element of an Iterable<? extends CharSequence> followed by a line separator | Files.write(path, List.of("a", "b")) writes a, newline, b, newline |
Files.newBufferedReader(path) / Files.newBufferedWriter(path, options...) | Return a BufferedReader / BufferedWriter | Close them with try-with-resources |
Files.newInputStream(path) / Files.newOutputStream(path) | Return byte streams | Bridge to classic java.io |
StandardOpenOption values change the behavior:
APPENDadds to the end of an existing file. Used alone, it throwsNoSuchFileExceptionif the file does not exist; combine it withCREATEto create the file when absent.CREATE_NEWfails withFileAlreadyExistsExceptionif the file already exists.TRUNCATE_EXISTINGempties an existing file before writing (part of the default forwriteStringandwrite).
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.
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?
Throws an IllegalArgumentException at runtime
/usr/local/bin/hosts
/etc/hosts
/usr/local/bin/etc/hosts
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?
Compiles and prints: /home/reports/2026/finance.xlsx, the joined path
Compiles and prints: ../../2026/finance.xlsx after normalizing
Compiles and prints: null, because the paths share no root
Throws IllegalArgumentException: one path is absolute, one relative
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?
/mammals/bear/polar.txt
zoo/mammals/bear
zoo/mammals
mammals/bear
Which statement correctly describes the behavior of Files.createDirectories(Path dir)?
It requires all parent directories to already exist and throws NoSuchFileException if any parent is missing.
It creates all non-existent parent directories as needed and completes normally without throwing an exception if the target directory already exists.
It deletes any existing directory before creating a fresh, empty directory structure.
It throws a DirectoryNotEmptyException if the target path is an existing non-empty directory.
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 FileAlreadyExistsException is thrown by the CREATE_NEW call, so nothing is printed.
[A, B, C] is printed.
A NoSuchFileException is thrown by the APPEND call.
[C] is printed, because CREATE_NEW replaces the existing file.
Sections you finish are checked off in the contents.