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.

Signature
public static Stream<Path> walk(Path start, int maxDepth, FileVisitOption... options) throws IOException
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$startPathThe directory (or file) to start from. It is the first element of the stream.
$maxDepthoptionalint= Integer.MAX_VALUEHow many levels to descend. 0 returns only start; 1 adds its direct entries. Leave it out to use walk(Path, FileVisitOption...) with no limit.
$optionsoptionalFileVisitOption...= links are not followedFileVisitOption.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 for orders.json. Use path.getFileName().toString().endsWith(".json").
  • Forgetting that walk includes the start directory, which then turns up in counts or gets passed to code that expects files. Filter with Files::isRegularFile.
  • Calling methods that throw IOException, such as Files.size, inside a lambda. Wrap the exception in UncheckedIOException, 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · java
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.

Example · java
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: 5000

Deleting 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.

Example · java
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

  • Be the first to comment on this lesson.