Contents

Path.of() (Java 11+) and its predecessor Paths.get() create an immutable Path from a string or a sequence of components. resolve() appends a relative path to an existing path, acting like a cd operation; if the argument is absolute it replaces the base entirely. relativize() computes the relative path between two absolute paths. normalize() collapses redundant . and .. components without touching the filesystem. toAbsolutePath() makes a relative path absolute using the current working directory. None of these methods require the path to exist on disk.

import java.nio.file.*; // Creating paths Path p1 = Path.of("/home/user/docs/report.txt"); // absolute Path p2 = Path.of("src", "main", "java", "App.java"); // relative, OS separator Path p3 = Path.of("."); // current directory // Path components System.out.println(p1.getFileName()); // report.txt System.out.println(p1.getParent()); // /home/user/docs System.out.println(p1.getRoot()); // / (null on Windows relative paths) System.out.println(p1.getNameCount()); // 4 System.out.println(p1.getName(2)); // docs // resolve — append a path (like cd) Path dir = Path.of("/home/user"); Path file = dir.resolve("docs/report.txt"); // /home/user/docs/report.txt Path abs = dir.resolve(Path.of("/etc/hosts")); // /etc/hosts (absolute overrides) // resolveSibling — resolve relative to parent Path sibling = p1.resolveSibling("summary.txt"); // /home/user/docs/summary.txt // relativize — compute relative path from one to another Path from = Path.of("/home/user"); Path to = Path.of("/home/user/docs/report.txt"); Path rel = from.relativize(to); // docs/report.txt // normalize — remove . and .. components Path messy = Path.of("/home/user/../user/./docs/report.txt"); System.out.println(messy.normalize()); // /home/user/docs/report.txt // toAbsolutePath — make relative path absolute (uses current directory) Path abs2 = Path.of("config.properties").toAbsolutePath(); // toRealPath — resolve symlinks, check existence // Path real = Path.of("link").toRealPath(); // throws NoSuchFileException if missing // startsWith / endsWith System.out.println(p1.startsWith("/home/user")); // true System.out.println(p1.endsWith("report.txt")); // true

Files.readString() (Java 11+) and Files.readAllBytes() load an entire file into memory in one call — appropriate for small configuration files and templates. Files.readAllLines() reads lines into a List<String>; for large files use Files.lines() instead, which returns a lazy stream that does not load all lines at once. On the write side, Files.writeString() and Files.write() are the simplest options; pass StandardOpenOption.APPEND to append rather than overwrite. All Files methods throw checked IOException on failure.

import java.nio.file.*; import java.nio.charset.StandardCharsets; Path file = Path.of("data.txt"); // Read entire file as String (Java 11+) — simplest option String content = Files.readString(file); String contentUtf8 = Files.readString(file, StandardCharsets.UTF_8); // Read all lines as List<String> List<String> lines = Files.readAllLines(file); // Read as bytes byte[] bytes = Files.readAllBytes(file); // Stream lines lazily (good for large files — doesn't load all at once) try (Stream<String> stream = Files.lines(file)) { long count = stream.filter(l -> l.contains("ERROR")).count(); } // Write entire file (Java 11+) Files.writeString(file, "Hello, World!\n"); Files.writeString(file, "Append me\n", StandardOpenOption.APPEND); // Write all lines Files.write(file, List.of("line1", "line2", "line3")); Files.write(file, List.of("more"), StandardOpenOption.APPEND); // Buffered reader/writer for streaming I/O try (var reader = Files.newBufferedReader(file)) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } try (var writer = Files.newBufferedWriter(file, StandardOpenOption.CREATE)) { writer.write("line 1"); writer.newLine(); writer.write("line 2"); } // Temporary files Path tempFile = Files.createTempFile("prefix-", ".tmp"); Path tempDir = Files.createTempDirectory("myapp-");

Files.copy() copies a file or stream to a destination; it fails by default if the destination exists unless you pass REPLACE_EXISTING. Note that copying a directory only creates the directory entry itself — it does not recursively copy its contents. Files.move() is atomic on the same filesystem and is the preferred way to implement safe file writes (write to a temp file, then atomically replace the target). Files.delete() throws NoSuchFileException if the path does not exist; Files.deleteIfExists() returns a boolean and does not throw in that case.

Path src = Path.of("src/data.txt"); Path dest = Path.of("dest/data.txt"); // Copy — fails if destination exists by default Files.copy(src, dest); // Copy with options Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING, // overwrite if exists StandardCopyOption.COPY_ATTRIBUTES); // preserve timestamps etc. // Copy from/to InputStream (useful for resources, downloads) try (InputStream in = SomeApi.getStream()) { Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING); } try (OutputStream out = new FileOutputStream("out.bin")) { Files.copy(src, out); } // Move (rename) — atomic on same filesystem Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING); // ATOMIC_MOVE — platform-dependent, throws AtomicMoveNotSupportedException if unavailable Files.move(src, dest, StandardCopyOption.ATOMIC_MOVE); // Delete Files.delete(dest); // throws NoSuchFileException if missing Files.deleteIfExists(dest); // no exception if missing // Delete a directory tree Files.walk(Path.of("target")) .sorted(Comparator.reverseOrder()) // delete children before parents .forEach(p -> { try { Files.delete(p); } catch (IOException e) { throw new RuntimeException(e); } }); // Create directories Files.createDirectory(Path.of("newdir")); // parent must exist Files.createDirectories(Path.of("a/b/c/d")); // creates all missing parents

Files.walk() returns a lazy Stream<Path> that traverses all descendants of a directory depth-first; always close it with try-with-resources. Files.find() combines the walk with a BiPredicate<Path, BasicFileAttributes> so you can filter on file attributes — size, modification time, type — without issuing separate stat calls per file. Both streams must be closed. For a non-recursive listing equivalent to ls, use Files.list().

Path root = Path.of("/home/user/projects"); // Walk — depth-first stream of all paths (files + directories) try (Stream<Path> walk = Files.walk(root)) { walk.filter(Files::isRegularFile) .filter(p -> p.toString().endsWith(".java")) .forEach(System.out::println); } // Walk with max depth try (Stream<Path> shallow = Files.walk(root, 2)) { // only goes 2 levels deep } // find — walk with a BiPredicate (more efficient than walk + filter) try (Stream<Path> found = Files.find(root, Integer.MAX_VALUE, (path, attrs) -> attrs.isRegularFile() && path.toString().endsWith(".java") && attrs.size() > 1024)) { found.forEach(System.out::println); } // list — non-recursive listing of a directory (like ls) try (Stream<Path> entries = Files.list(root)) { entries.filter(Files::isDirectory) .forEach(System.out::println); } // FileVisitor — full control over traversal Files.walkFileTree(root, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { System.out.println("File: " + file + " (" + attrs.size() + " bytes)"); return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if (dir.getFileName().toString().equals(".git")) { return FileVisitResult.SKIP_SUBTREE; // skip .git directories } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { System.err.println("Failed to visit: " + file + " - " + exc); return FileVisitResult.CONTINUE; // continue despite error } });

The Files utility class provides individual helper methods for common checks: Files.size(), Files.isRegularFile(), Files.isDirectory(), Files.isReadable(), and Files.isWritable(). For multiple attributes at once, Files.readAttributes(path, BasicFileAttributes.class) fetches creation time, last-modified time, last-access time, size, and type in a single OS call. On POSIX systems (Linux, macOS), PosixFileAttributes additionally exposes owner, group, and permission bits. Use Files.getLastModifiedTime() as a shortcut when you only need the timestamp.

import java.nio.file.attribute.*; Path file = Path.of("report.txt"); // Quick checks boolean exists = Files.exists(file); boolean isFile = Files.isRegularFile(file); boolean isDir = Files.isDirectory(file); boolean readable = Files.isReadable(file); boolean writable = Files.isWritable(file); boolean hidden = Files.isHidden(file); long size = Files.size(file); // throws if not exists // Basic attributes — all at once (single OS call) BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class); System.out.println("Size: " + attrs.size()); System.out.println("Created: " + attrs.creationTime()); System.out.println("Modified: " + attrs.lastModifiedTime()); System.out.println("Accessed: " + attrs.lastAccessTime()); System.out.println("Is symlink: " + attrs.isSymbolicLink()); // Set modification time Files.setLastModifiedTime(file, FileTime.fromMillis(System.currentTimeMillis())); // POSIX attributes (Linux/macOS) — owner, group, permissions if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) { PosixFileAttributes posix = Files.readAttributes(file, PosixFileAttributes.class); System.out.println("Owner: " + posix.owner().getName()); System.out.println("Group: " + posix.group().getName()); System.out.println("Perms: " + PosixFilePermissions.toString(posix.permissions())); // e.g., "rw-r--r--" // Set permissions Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-rw-r--"); Files.setPosixFilePermissions(file, perms); } // User-defined attributes (extended attributes) UserDefinedFileAttributeView view = Files.getFileAttributeView(file, UserDefinedFileAttributeView.class); ByteBuffer buf = ByteBuffer.allocate(64); view.write("myapp.checksum", ByteBuffer.wrap("sha256:abc123".getBytes())); buf.clear(); view.read("myapp.checksum", buf); buf.flip(); System.out.println(StandardCharsets.UTF_8.decode(buf)); // sha256:abc123 Prefer Path and Files over the legacy java.io.File for all new code. File has many design problems: methods return boolean instead of throwing exceptions on failure, no symbolic link support, and limited attribute access. Path.toFile() and File.toPath() bridge between the two APIs when needed.