Java.nio: most concise recursive directory delete

fgysin reinstate Monica picture fgysin reinstate Monica · Mar 14, 2016 · Viewed 42.2k times · Source

I am currently trying to recursively delete a directory... Strangely enough the shortest piece of code I was able to find is the following construct, employing an ad-hoc inner class and in a visitor pattern...

Path rootPath = Paths.get("data/to-delete");

try {
  Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      System.out.println("delete file: " + file.toString());
      Files.delete(file);
      return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
      Files.delete(dir);
      System.out.println("delete dir: " + dir.toString());
      return FileVisitResult.CONTINUE;
    }
  });
} catch(IOException e){
  e.printStackTrace();
}

Source: here

This feels horribly clumsy and verbose, given that the new nio APIs remove so much clutter and boilerplate...

Is there any shorter way of achieving a forced, recursive directory delete?

I'm looking for pure native Java 1.8 methods, so please don't link to external libraries...

Answer

SubOptimal picture SubOptimal · Mar 14, 2016

You can combine NIO 2 and the Stream API.

Path rootPath = Paths.get("/data/to-delete");
// before you copy and paste the snippet
// - read the post till the end
// - read the javadoc to understand what the code will do 
//
// a) to follow softlinks (removes the linked file too) use
// Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS)
//
// b) to not follow softlinks (removes only the softlink) use
// the snippet below
try (Stream<Path> walk = Files.walk(rootPath)) {
    walk.sorted(Comparator.reverseOrder())
        .map(Path::toFile)
        .peek(System.out::println)
        .forEach(File::delete);
}
  • Files.walk - return all files/directories below rootPath including
  • .sorted - sort the list in reverse order, so the directory itself comes after the including subdirectories and files
  • .map - map the Path to File
  • .peek - is there only to show which entry is processed
  • .forEach - calls the .delete() method on every File object

EDIT As first mentioned by @Seby and now cited by @John Dough the Files.walk() should be used in a try-with-resource construct. Thanks to both.

From Files.walk javadoc

If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream's close method is invoked after the stream operations are completed.

EDIT

Here are some figures.
The directory /data/to-delete contained the unpacked rt.jar of jdk1.8.0_73 and a recent build of activemq.

files: 36,427
dirs :  4,143
size : 514 MB

Times in milliseconds

                    int. SSD     ext. USB3
NIO + Stream API    1,126        11,943
FileVisitor         1,362        13,561

Both version were executed without printing file names. The most limiting factor is the drive. Not the implementation.

EDIT

Some addtional information about tthe option FileVisitOption.FOLLOW_LINKS.

Assume following file and directory structure

/data/dont-delete/bar
/data/to-delete/foo
/data/to-delete/dont-delete -> ../dont-delete

Using

Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS)

will follow symlinks and the file /tmp/dont_delete/bar would be deleted as well.

Using

Files.walk(rootPath)

will not follow symlinks and the file /tmp/dont_delete/bar would not be deleted.

NOTE: Never use code as copy and paste without understanding what it does.