Skip to content

Delete a directory in Java

How do you delete a directory in Java? java.io.File defines a method delete() that deletes a file or directory:

public void delete(File dir) {
    dir.delete();
}

If only it were that simple.

java.io.File has been in the Java Standard Edition SDK since version 1. Its design is not ideal but it can’t be changed now without breaking backward compatibility. Javadoc comments in version 11 suggests that java.nio.file.Files has better error handling:

The java.nio.file.Files class defines the delete method to throw an IOException when a file cannot be deleted. This is useful for error reporting and to diagnose why a file cannot be deleted.

If you do choose to use File.delete, always check the boolean return value and handle failure appropriately:

public void delete(File dir) throws IOException {
    boolean success = dir.delete();
    if (!success) throw new IOException("Failed to delete " + dir);
}

java.nio.file.Files delete()

Java NIO.2 (New IO) was added in Java 7 to address some shortcomings of the file handling in the original version of Java. NIO.2’s directory delete method throws an exception on failure so is usually preferred:

public void delete(File dir) throws IOException {
    Files.delete(dir.toPath());
}

The exception type and message should provide useful information about the cause of failure. For example, if you attempt to delete a non-empty directory, you get:

java.nio.file.DirectoryNotEmptyException: /tmp/nonEmptyDir

This leads us to the next major shortcoming of java.io.File and NIO.2 java.nio.file.Files delete methods. They both fail to delete directories that contain files.

Delete a non-empty directory in Java

Java does not natively allow you delete a non-empty directory so you’ll need to empty the directory first. That is, find every file and subdirectory and delete them before deleting the directory itself. Obviously, you’ll need to empty subdirectories before you delete them too.

As of Java 8 with NIO.2 and Streams, this is fairly easy to do:

public void delete(File dir) throws IOException {
    try (Stream<Path> paths = Files.walk(dir.toPath())) {
        paths.sorted(Comparator.reverseOrder())
                .map(Path::toFile)
                .forEach(File::delete);
    }
}

Alternatively, use Apache Commons IO to do this for you (Spring and Guava have similar utilities):

public void delete(File dir) throws IOException {
    org.apache.commons.io.FileUtils.deleteDirectory(dir);
}

Overriding file permissions

Java is designed to run on any operating system. From here on though, assume we’re on a UNIX / Linux OS.

The above methods will allow you delete any directory system where the application user has write permission on the directory and its contents AND has execute permission on the directory and subdirectories. In short, you can delete it if you own it and have rwx permission.

An odd edge case here is a (empty) directory without execute permission. At command line you could delete the directory with rmdir or rm -r. java.io.File.delete() and java.nio.file.Files.delete() also work. However, the Files.walk() solution and Apache Commons IO both fail as they attempt to traverse the directory before deleting – even though it’s empty.

Lets consider though a directory that is owned by another user but the current user could delete it with elevated privileges. That is, if you were doing this at command line, rm -rf would not work but sudo rm -rf would.

Let’s be clear that we are subverting expected behaviours here. If your application does not have permission on a file, we’d normally expect it not to be able to delete it. However if you own the file but have read only permission, you might want to force the delete – similar to rm -rf.

A solution here is to traverse the directory’s contents and ‘fix’ permissions before you attempt to delete:

@Override
public void delete(File dir) throws IOException {
    fixPermission(dir.toPath());
    Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>(){
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            File[] subdirs = dir.toFile().listFiles(File::isDirectory);
            if (subdirs == null) return FileVisitResult.CONTINUE;
            for (File subdir : subdirs) {
                fixPermission(subdir.toPath());
            }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }
    });
}

You can ‘fix’ permissions on a file or directory like this:

static final Set<PosixFilePermission> REQUIRED_PERMISSIONS = Set.of(
            PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_WRITE);

private void fixPermission(Path dir) throws IOException {
    Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(dir);
    if (!permissions.containsAll(REQUIRED_PERMISSIONS)) {
        permissions.addAll(REQUIRED_PERMISSIONS);
        Files.setPosixFilePermissions(dir, permissions);
    }
}

This requires that your user has sufficient privileges to change permission of a file.

How not to do it

If this all seems needlessly complicated, why not just invoke the sudo rm -rf command from Java?

public void delete(File dir) throws IOException {
    // DON'T DO THIS!!
    Runtime.runtime.exec("sudo rm -rf " + dir.getPath());
}

It’s just one line of code and takes care of every edge case I can think of.

There are a number of reasons why this is a bad idea:

  1. Requires your program to run as root OR a user with root privileges AND no password required to sudo. Anyone who owns this program owns your system.
  2. Command injection vulnerability. dir.getPath() is untrusted input. If a malicious user can create a file name that terminates the command and starts a new one (yes, semicolons are valid characters in file names!) they can piggy-back their commands onto yours. At the very least, use ProcessBuilder to prevent command injection.
  3. If the command does not complete, it causes your application to hang. Workarounds include running this asynchronously in a new thread and/or implementing a timeout. You’ll need to handle the timeout condition and InterruptedException (careful!).
  4. Error handling. You won’t get useful error messages. Best you can do is check for a non-zero exit code and throw an exception.

A somewhat improved solution:

public void delete(File dir) throws IOException {
    Process proc = null;
    try {
        proc = new ProcessBuilder("sudo", "rm", "-rf", dir.getPath()).start();
        boolean done = proc.waitFor(10, TimeUnit.SECONDS);
        if (proc.exitValue() != 0) {
            throw new IOException("Non-zero exit code");
        }
        if (!done) {
            proc.destroyForcibly();
        }
    } catch (InterruptedException e)  {
        Thread.currentThread().interrupt();
        throw new IOException(e);
    } finally {
        if (proc != null) proc.destroyForcibly();
    }
}

Again though, this is best avoided as you’ll need to run the application as a user who can sudo without entering a password – extremely risky.

Delete a directory in Java – all the solutions

Code for all of these implementations is in my delete directory demo on GitHub. It has all the implementations described above plus tests for the various edge cases we’ve considered.

Published inJava Basics

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *