Contents
- Path: Creating, Resolving, Relativizing
- Reading and Writing Files
- Copy, Move, and Delete
- Walking and Finding Files
- File Attributes and Metadata
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.