Path
Path is the central interface in java.nio.file, introduced in Java 7 as part of NIO.2, representing a location in a file system as a sequence of path elements separated by a delimiter. Unlike java.io.File, Path is an interface backed by a pluggable FileSystem provider — the default provider maps to the OS file system, but ZIP archives, in-memory file systems, and remote file systems can provide their own Path implementations. Path is immutable and does not represent an open file or an existing file system entry; it is purely a path string with structured access and manipulation methods. Path provides substantially better semantics than File: path component access, resolution, relativization, normalization, and iteration over path elements are all first-class operations. Combined with the Files utility class, Path enables all file system operations with proper IOException-throwing semantics. This entry covers all Path construction methods, every path manipulation method in depth, path resolution and relativization with edge cases, normalization and toRealPath(), path iteration and watching, conversion to URI and File, comparison semantics, and cross-platform path handling.
Path Construction, Components, and Basic Navigation
// ── Path construction ─────────────────────────────────────────────────
// Modern (Java 11+):
Path p1 = Path.of("/home/user/documents/report.txt");
Path p2 = Path.of("/home", "user", "documents", "report.txt"); // joined with /
Path p3 = Path.of("relative/path/file.txt"); // relative to CWD
// Older (Java 7+):
Path p4 = Paths.get("/home/user/documents/report.txt");
// From URI:
Path p5 = Path.of(URI.create("file:///home/user/report.txt"));
// From File (interop):
Path p6 = new File("/home/user/report.txt").toPath();
// ── Component access ──────────────────────────────────────────────────
Path p = Path.of("/home/user/documents/report.txt");
System.out.println(p.getFileName()); // report.txt (Path, not String)
System.out.println(p.getParent()); // /home/user/documents
System.out.println(p.getRoot()); // / (null for relative paths)
System.out.println(p.getNameCount()); // 4
System.out.println(p.getName(0)); // home
System.out.println(p.getName(1)); // user
System.out.println(p.getName(2)); // documents
System.out.println(p.getName(3)); // report.txt
// subpath — relative Path from index range:
System.out.println(p.subpath(1, 3)); // user/documents (no leading /)
// isAbsolute:
System.out.println(Path.of("/absolute").isAbsolute()); // true
System.out.println(Path.of("relative").isAbsolute()); // false
// toAbsolutePath: prepends CWD for relative paths
Path rel = Path.of("data/file.txt");
System.out.println(rel.toAbsolutePath()); // /current/working/dir/data/file.txt
// ── Bare filename has null parent and root ────────────────────────────
Path bare = Path.of("file.txt");
System.out.println(bare.getParent()); // null — no parent component
System.out.println(bare.getRoot()); // null — no root (relative)
System.out.println(bare.getFileName()); // file.txt
System.out.println(bare.getNameCount()); // 1
// ── Root-only path ────────────────────────────────────────────────────
Path root = Path.of("/");
System.out.println(root.getParent()); // null — root has no parent
System.out.println(root.getRoot()); // /
System.out.println(root.getFileName()); // null — no filename component
System.out.println(root.getNameCount()); // 0
// ── Path iteration ────────────────────────────────────────────────────
Path longPath = Path.of("/home/user/documents/projects/java/report.txt");
for (Path element : longPath) {
System.out.println(element); // each name component as a relative Path
}
// home, user, documents, projects, java, report.txt
// getFileName() as String:
String filename = longPath.getFileName().toString(); // "report.txt"
// Extract extension:
int dot = filename.lastIndexOf('.');
String ext = dot > 0 ? filename.substring(dot + 1) : ""; // "txt"resolve(), relativize(), normalize(), and toRealPath()
// ── resolve() — appending paths ──────────────────────────────────────
Path base = Path.of("/home/user");
// Relative resolution — append:
System.out.println(base.resolve("documents/report.txt"));
// /home/user/documents/report.txt
// Absolute resolution — other replaces base entirely:
System.out.println(base.resolve("/tmp/file.txt"));
// /tmp/file.txt (absolute path ignores base)
// Empty resolution — returns base unchanged:
System.out.println(base.resolve(""));
// /home/user
// Building paths step by step:
Path config = Path.of("/etc")
.resolve("myapp")
.resolve("config")
.resolve("settings.json");
System.out.println(config); // /etc/myapp/config/settings.json
// ── resolveSibling() — replace last component ─────────────────────────
Path original = Path.of("/home/user/document.txt");
System.out.println(original.resolveSibling("backup.txt")); // /home/user/backup.txt
System.out.println(original.resolveSibling("archive")); // /home/user/archive
// Practical: change file extension
Path changeExt(Path p, String newExt) {
String name = p.getFileName().toString();
String baseName = name.contains(".")
? name.substring(0, name.lastIndexOf('.'))
: name;
return p.resolveSibling(baseName + "." + newExt);
}
System.out.println(changeExt(Path.of("/docs/report.txt"), "pdf"));
// /docs/report.pdf
// ── relativize() — computing relative paths ───────────────────────────
Path from = Path.of("/home/user");
Path to = Path.of("/home/user/documents/report.txt");
System.out.println(from.relativize(to)); // documents/report.txt
// Moving up the tree:
Path from2 = Path.of("/home/user/project");
Path to2 = Path.of("/home/user/documents/report.txt");
System.out.println(from2.relativize(to2)); // ../documents/report.txt
// Completely different trees:
Path from3 = Path.of("/a/b/c");
Path to3 = Path.of("/x/y/z");
System.out.println(from3.relativize(to3)); // ../../../x/y/z
// Round-trip property: resolve(relativize(target)) == target
assert from.resolve(from.relativize(to)).normalize().equals(to.normalize());
// ── normalize() — string-level, no file system access ─────────────────
System.out.println(Path.of("/home/user/../user/./docs").normalize());
// /home/user/docs
System.out.println(Path.of("a/b/../../c").normalize());
// c
System.out.println(Path.of("./relative/../path").normalize());
// path
// normalize() doesn't resolve symlinks:
// /symlink-to-usr/../etc after normalize: /etc
// But if /symlink-to-usr -> /usr, the canonical path would be /etc (correct)
// or /usr/../etc -> /etc (same result here but not always)
// ── toRealPath() — file system access, resolves symlinks ──────────────
try {
Path real = Path.of("/home/user/./docs/../documents/../docs")
.toRealPath(); // accesses file system; throws IOException if not found
System.out.println("Real path: " + real);
// NOFOLLOW_LINKS: resolve ".." and "." but don't follow symlinks
Path noFollow = Path.of("/home/user/symlink/../other")
.toRealPath(LinkOption.NOFOLLOW_LINKS);
} catch (IOException e) {
System.err.println("Path doesn't exist: " + e.getMessage());
}
// ── startsWith() and endsWith() — component-based ─────────────────────
Path p = Path.of("/home/user/documents/report.txt");
System.out.println(p.startsWith("/home/user")); // true
System.out.println(p.startsWith("/home/use")); // false — not a component boundary
System.out.println(p.startsWith(Path.of("/home"))); // true
System.out.println(p.endsWith("report.txt")); // true
System.out.println(p.endsWith("documents/report.txt")); // true
System.out.println(p.endsWith("ort.txt")); // falseConversion, Comparison, Watching, and Cross-Platform Handling
// ── Conversion methods ────────────────────────────────────────────────
Path p = Path.of("/home/user/documents/report.txt");
// To URI: always absolute, always forward slashes
URI uri = p.toUri();
System.out.println(uri); // file:///home/user/documents/report.txt
// Back to Path from URI:
Path fromUri = Path.of(uri);
System.out.println(fromUri.equals(p)); // true
// To File (for legacy APIs):
File file = p.toFile();
// From File (to use NIO.2):
Path fromFile = file.toPath();
System.out.println(p.equals(fromFile)); // true
// toString:
System.out.println(p.toString()); // /home/user/documents/report.txt (Unix)
// home/userdocuments
eport.txt (Windows)
// ── Path comparison ────────────────────────────────────────────────────
Path a = Path.of("/home/user/file.txt");
Path b = Path.of("/home/user/file.txt");
Path c = Path.of("/HOME/USER/FILE.TXT");
System.out.println(a.equals(b)); // true — same path string
System.out.println(a.equals(c)); // false on Unix (case-sensitive string comparison)
// may be false even on case-insensitive Windows FS
// Files.isSameFile — actual file system identity:
try {
boolean same = Files.isSameFile(a, c); // true on case-insensitive FS
// Follows symlinks, checks inodes — the only correct identity comparison
} catch (IOException e) { /* path doesn't exist */ }
// Sorting paths:
List<Path> paths = Arrays.asList(
Path.of("/home/user/z.txt"),
Path.of("/home/user/a.txt"),
Path.of("/home/user/m.txt")
);
Collections.sort(paths); // compareTo() — platform-consistent ordering
// Result: /home/user/a.txt, /home/user/m.txt, /home/user/z.txt
// ── ZIP file system — treating ZIP as a directory ─────────────────────
Path zipFile = Path.of("/home/user/archive.zip");
try (FileSystem zipFs = FileSystems.newFileSystem(zipFile)) {
Path zipRoot = zipFs.getPath("/"); // root of the ZIP
// List ZIP contents:
try (Stream<Path> walk = Files.walk(zipRoot)) {
walk.forEach(System.out::println); // /dir/, /dir/file.txt, etc.
}
// Read a file from ZIP:
Path entry = zipFs.getPath("/config/settings.json");
String content = Files.readString(entry, StandardCharsets.UTF_8);
// Copy file from ZIP to real FS:
Files.copy(entry, Path.of("/tmp/settings.json"),
StandardCopyOption.REPLACE_EXISTING);
// Add a file to ZIP:
Path newEntry = zipFs.getPath("/newfile.txt");
Files.writeString(newEntry, "Hello from ZIP!", StandardCharsets.UTF_8);
}
// ── Cross-platform path construction ─────────────────────────────────
// BEST: use multi-segment form, let FileSystem handle separators:
Path crossPlatform = Path.of("data", "config", "settings.json");
// On Unix: data/config/settings.json
// On Windows: dataconfigsettings.json
// From user input — handles both / and on Windows:
String userInput = "data/config\settings.json"; // mixed separators
Path normalized = Path.of(userInput); // FileSystem normalizes
// Joining user-provided path with base:
Path base2 = Path.of("/var/myapp");
String userRelative = "uploads/image.png";
Path safe = base2.resolve(userRelative).normalize();
// Security check: ensure result is under base
if (!safe.startsWith(base2)) {
throw new SecurityException("Path traversal attempt: " + userRelative);
}
// ── Path as TreeMap key — ordering and equality ───────────────────────
TreeMap<Path, String> pathMap = new TreeMap<>();
pathMap.put(Path.of("/home/user/b.txt"), "B");
pathMap.put(Path.of("/home/user/a.txt"), "A");
pathMap.put(Path.of("/home/user/c.txt"), "C");
pathMap.forEach((k, v) -> System.out.println(k + " = " + v));
// /home/user/a.txt = A
// /home/user/b.txt = B
// /home/user/c.txt = C (sorted by compareTo)