Files.list() and Files.walk()
List a directory's entries or walk a whole tree as a Stream of Paths, filter by type and name, and close the stream.
public static Stream<Path> walk(Path start, int maxDepth, FileVisitOption... options) throws IOException- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$start | Path | The directory (or file) to start from. It is the first element of the stream. |
$maxDepthoptional | int= Integer.MAX_VALUE | How many levels to descend. 0 returns only start; 1 adds its direct entries. Leave it out to use walk(Path, FileVisitOption...) with no limit. |
$optionsoptional | FileVisitOption...= links are not followed | FileVisitOption.FOLLOW_LINKS to descend into symbolic links to directories. |
Return value
Stream<Path>A lazily populated stream of every path in the tree, starting with start itself, visited depth first. The stream holds open directories and must be closed.
Errors
IOException if start cannot be opened (NoSuchFileException when it does not exist); IllegalArgumentException for a negative maxDepth. Errors met during the walk are thrown as UncheckedIOException from the stream operation; FileSystemLoopException (wrapped) when FOLLOW_LINKS finds a cycle.
Both methods return a Stream<Path>, added in Java 8. Files.list(dir) gives the direct entries of one directory, files and subdirectories alike, but not dir itself and nothing deeper. Files.walk(start) visits the whole tree depth first and includes start as the first element. Files.walk(start, 1) is list plus the start directory.
Close the stream
The streams hold open directory handles until closed. Always use try-with-resources, exactly as with Files.lines. Leaking them in a scheduled cleanup job that runs every minute ends in Too many open files.
try (Stream<Path> paths = Files.walk(root)) {
List<Path> json = paths.filter(Files::isRegularFile)
.filter(p -> p.getFileName().toString().endsWith(".json"))
.toList();
}Order is not guaranteed
Entries come back in whatever order the file system returns them, which differs between Linux, macOS and Windows and can change as files are added. Sort when the order matters, for example when processing import files oldest first or when a test compares output.
Mistakes people make
- Path.endsWith is not String.endsWith.
path.endsWith(".json")compares whole name elements, so it is false fororders.json. Usepath.getFileName().toString().endsWith(".json"). - Forgetting that
walkincludes the start directory, which then turns up in counts or gets passed to code that expects files. Filter withFiles::isRegularFile. - Calling methods that throw
IOException, such asFiles.size, inside a lambda. Wrap the exception inUncheckedIOException, or collect to a list first and loop. - Deleting a tree in walk order. Parents come before their children, so delete in reverse order.
If an entry cannot be read part-way through, for example because of permissions, the walk stops with an UncheckedIOException. When you need to skip unreadable directories and carry on, use Files.walkFileTree with a FileVisitor. For name patterns, Files.newDirectoryStream(dir, "*.csv") filters one directory with a glob, and Files.find walks with a predicate that also receives the file attributes.
Example
When to use it
- Pick up every pending .csv or .json file in an import directory for a batch job, oldest name first.
- Compute the disk space used under each tenant's upload directory for a storage quota.
- Remove a job's temporary working directory and everything in it after processing finishes.
Frequently asked questions
How do I list all files in a directory recursively in Java?
Use try (Stream<Path> paths = Files.walk(dir)) { paths.filter(Files::isRegularFile).forEach(...); }. Files.list returns only the direct entries of one directory.
What order does Files.walk return files in?
Depth first, with each directory before its contents, but the order of entries within a directory is whatever the file system returns. Sort the stream when you need a stable order.
Why do I get UncheckedIOException from Files.walk?
An entry could not be read during the walk, often a directory without read permission. The stream wraps the IOException. Use Files.walkFileTree with a visitor that overrides visitFileFailed if you need to skip such entries.
More examples
Finding files by extension
Path.endsWith(".json") asks whether the last path element is named exactly .json, so it matches nothing here. Comparing the file name as a String finds the two JSON files and correctly skips the .bak copy.
void main() throws IOException {
Path root = Files.createTempDirectory("import");
try {
Files.createDirectories(root.resolve("pending"));
Files.writeString(root.resolve("pending/orders.json"), "[]");
Files.writeString(root.resolve("pending/orders.json.bak"), "[]");
Files.writeString(root.resolve("customers.json"), "[]");
try (Stream<Path> tree = Files.walk(root)) {
List<Path> all = tree.filter(Files::isRegularFile).toList();
long wrong = all.stream().filter(p -> p.endsWith(".json")).count();
IO.println("Path.endsWith matches: " + wrong);
List<String> json = all.stream()
.filter(p -> p.getFileName().toString().endsWith(".json"))
.map(p -> root.relativize(p).toString())
.sorted()
.toList();
IO.println("String endsWith matches: " + json);
}
} finally {
try (Stream<Path> tree = Files.walk(root)) {
for (Path p : tree.sorted(Comparator.reverseOrder()).toList()) {
Files.delete(p);
}
}
}
}
// Output:
// Path.endsWith matches: 0
// String endsWith matches: [customers.json, pending/orders.json]Total size of a directory
Files.size throws a checked IOException, which a lambda cannot throw, so a small helper rethrows it as UncheckedIOException. A per-tenant storage quota check is this pipeline with a limit at the end.
long sizeOf(Path file) {
try {
return Files.size(file);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
void main() throws IOException {
Path root = Files.createTempDirectory("media");
try {
Files.createDirectories(root.resolve("avatars"));
Files.write(root.resolve("avatars/1.png"), new byte[1500]);
Files.write(root.resolve("avatars/2.png"), new byte[2500]);
Files.write(root.resolve("logo.svg"), new byte[1000]);
try (Stream<Path> tree = Files.walk(root)) {
long total = tree.filter(Files::isRegularFile).mapToLong(this::sizeOf).sum();
IO.println("total bytes: " + total);
}
} finally {
try (Stream<Path> tree = Files.walk(root)) {
for (Path p : tree.sorted(Comparator.reverseOrder()).toList()) {
Files.delete(p);
}
}
}
}
// Output:
// total bytes: 5000Deleting a tree in reverse order
walk visits parents first, but a directory can only be deleted once it is empty. Sorting the paths in reverse puts longer, deeper paths before the directories that contain them.
void deleteTree(Path root) throws IOException {
try (Stream<Path> tree = Files.walk(root)) {
// Reverse order puts every child before its parent directory.
for (Path p : tree.sorted(Comparator.reverseOrder()).toList()) {
Files.delete(p);
}
}
}
void main() throws IOException {
Path root = Files.createTempDirectory("build");
Files.createDirectories(root.resolve("classes/com/example"));
Files.writeString(root.resolve("classes/com/example/App.class"), "x");
Files.writeString(root.resolve("app.jar"), "x");
try (Stream<Path> tree = Files.walk(root)) {
IO.println("entries before: " + tree.count());
}
deleteTree(root);
IO.println("root exists after: " + Files.exists(root));
}
// Output:
// entries before: 6
// root exists after: false
Discussion