How do I use Files.walk() to traverse directories?

To use Files.walk to traverse directories, you call the method with a starting Path. It returns a Stream<Path> that lazily populates as you traverse the file tree in a depth-first manner.

The most important best practice when using Files.walk is to use it within a try-with-resources block. This ensures that the underlying resources (the directory stream) are closed properly after the operation completes.

Basic Usage Example

Here is how you can list every file and directory starting from a specific path:

package org.kodejava.nio;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class WalkExample {
    public static void main(String[] args) {
        Path startPath = Paths.get(".");

        try (Stream<Path> stream = Files.walk(startPath)) {
            stream.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Filtering and Customizing the Traversal

  1. Limiting Depth: You can provide a maxDepth argument to control how many levels deep the traversal should go.
    // Only traverse up to 2 levels deep
    try (Stream<Path> stream = Files.walk(startPath, 2)) {
        stream.forEach(System.out::println);
    }
    
  2. Filtering for Files only: Use the filter method on the Stream to exclude directories.
    try (Stream<Path> stream = Files.walk(startPath)) {
        stream.filter(Files::isRegularFile)
              .forEach(System.out::println);
    }
    
  3. Handling Symbolic Links: By default, Files.walk does not follow symbolic links. You can enable this by passing FileVisitOption.FOLLOW_LINKS.
    import java.nio.file.FileVisitOption;
    
    // ...
    try (Stream<Path> stream = Files.walk(startPath, FileVisitOption.FOLLOW_LINKS)) {
        stream.forEach(System.out::println);
    }
    

Key Considerations

  • IOException: Unlike many Stream operations, Files.walk can throw an IOException during initialization. Also, if an error occurs during iteration (e.g., a permission issue), it will throw an UncheckedIOException.
  • Memory Efficiency: Because it returns a Stream, it is memory-efficient for large directory structures as it doesn’t load all paths into memory at once.
  • Alternatives: If you need more control (like specific logic when entering a directory or handling errors for specific files), consider using Files.walkFileTree with a FileVisitor.

How to recursively rename files with a specific suffix in Java?

The following code snippet show you how to recursively rename files with a specific suffix. In this example we are renaming a collection of resource bundles files which ends with _in.properties into _id.properties. The code snippet also count the number of files affected by the process. We use the Files.move() method to rename the file, if you want to copy the files instead of renaming them, then you can use the Files.copy() method.

Here is the complete code snippet:

package org.kodejava.io;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

public class RenameResourceBundles {
    public static void main(String[] args) {
        String startDirectory = "C:/Projects/Hello";
        AtomicInteger counter = new AtomicInteger(0);

        try (Stream<Path> paths = Files.walk(Paths.get(startDirectory))) {
            paths.filter(Files::isRegularFile)
                    .filter(path -> path.toString().endsWith("_in.properties"))
                    .forEach(path -> renameFile(path, counter));
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("Total files renamed: " + counter.get());
    }

    private static void renameFile(Path path, AtomicInteger counter) {
        try {
            String newName = path.toString().replace("_in.properties", "_id.properties");
            Path newPath = Paths.get(newName);
            Files.move(path, newPath);
            System.out.println("Renamed: " + path + " to " + newPath);
            counter.incrementAndGet();
        } catch (IOException e) {
            System.out.println("Failed to rename: " + path);
            e.printStackTrace();
        }
    }
}

This code will recursively search through all subdirectories starting from the specified root directory and rename any files that end with _in.properties to _id.properties. The process prints the renamed file, and finally outputs the total number of files that were successfully renamed after traversing the directory tree.

The explanation of the code snippet above:

  • The Files.walk method is used to traverse the directory tree starting from the given directory.
  • The filter method is used to select only regular files that end with _in.properties.
  • The renameFile method handles the renaming of each file, replacing _in.properties with _id.properties.
  • An AtomicInteger named counter keeps track of the number of files renamed. AtomicInteger is used to handle the count in a thread-safe manner, which is useful if the code is ever modified to use parallel streams or multi-threading.
  • Inside the renameFile method, counter.incrementAndGet() is called each time a file is successfully renamed. This increments the counter by one.
  • After the Files.walk operation, the total number of renamed files is printed using System.out.println("Total files renamed: " + counter.get());.