Java Delete Directory Recursively: Files.walk() and More

Java delete directory recursively with Files.walk() or walkFileTree(), plus Files.delete() for single files, symbolic links, library options and failures.

Folder tree of a cache directory with the deletion order produced by Files.walk sorted in reverse order

To delete a directory recursively in Java, we list the whole tree with Files.walk(), sort the paths in reverse order so that every file and subfolder comes before its parent, and call Files.delete() on each path. We need the extra steps because Files.delete() removes only files and empty folders.

Apps delete folder trees to clean up build output, remove extracted uploads after processing, drop a user’s files when the account is closed and tidy up after tests. The JDK has no single-call method for this, so we write the walk ourselves or use a library.

The following example creates a small cache folder with a nested file and deletes the whole tree.

Path cache = Files.createTempDirectory("cache");
Path thumbs = Files.createDirectories(cache.resolve("thumbs/2026"));    // nested folders
Path image = Files.writeString(thumbs.resolve("cat.jpg"), "bytes");      // a file inside
try (Stream<Path> tree = Files.walk(cache)) {
    for (Path path : tree.sorted(Comparator.reverseOrder()).toList()) {
        Files.delete(path);                                              // children first
    }
}
boolean gone = Files.notExists(cache);                                   // true

Notice that the stream from Files.walk() sits in try-with-resources, and that a failed delete throws an exception instead of being ignored. After the tree version, we look at Files.walkFileTree() for large trees, symbolic links, deleting a single file or an empty folder, emptying a folder without removing it, library methods and the reasons a delete fails.

1. Delete a Directory Recursively With Files.walk()

The Files.walk() method produces a lazy Stream<Path> that starts with the folder itself and visits every entry under it after that, parents before children. We need the opposite order, because Files.delete() throws DirectoryNotEmptyException for a folder that still has content.

Sorting with Comparator.reverseOrder() gives that order. A child path such as cache/thumbs/2026 starts with its parent cache/thumbs, so it is greater in the natural Path order, and the reverse order puts it first.

Folder tree of a cache directory with the deletion order produced by Files.walk sorted in reverse order
After sorted(Comparator.reverseOrder()), every file and subfolder is deleted before the folder that contains it, and the root goes last

We wrap the pattern in a method and let IOException reach the caller, so a file that cannot be deleted stops the operation with a clear message.

static void deleteRecursively(Path dir) throws IOException {
    try (Stream<Path> tree = Files.walk(dir)) {
        for (Path path : tree.sorted(Comparator.reverseOrder()).toList()) {
            Files.delete(path);
        }
    }
}
Path build = Files.createTempDirectory("build");
Path classes = Files.createDirectories(build.resolve("classes/com/shop"));
Path main = Files.writeString(classes.resolve("Main.class"), "x");
Path jar = Files.writeString(build.resolve("shop.jar"), "x");
deleteRecursively(build);
boolean buildGone = Files.notExists(build);                  // true

The old version of this pattern, still common in tutorials, ends with .map(Path::toFile).forEach(File::delete). It compiles, but File.delete() returns false on failure, and forEach() drops that result, so the method reports success while files stay on disk. Many copies also never close the stream, which keeps directory handles open.

Sorting needs all paths in memory, which is fine for thousands of entries. For trees with millions of files, the visitor in the next section deletes as it walks.

2. Deleting With Files.walkFileTree()

The method Files.walkFileTree() calls our FileVisitor for every entry. We delete each file in visitFile() and each folder in postVisitDirectory(), which runs after the visitor has handled everything inside that folder. Nothing is collected in a list, so memory stays flat for any tree size.

Say a photo sharing app deletes a closed account, and the account folder holds 300,000 images in nested album folders. The visitor version removes them one by one without building a list of 300,000 paths first.

static void deleteTree(Path root) throws IOException {
    Files.walkFileTree(root, new SimpleFileVisitor<>() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            if (exc != null) {
                throw exc;                    // a folder could not be read
            }
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        }
    });
}
Path account = Files.createTempDirectory("account-7");
Path album = Files.createDirectories(account.resolve("albums/summer"));
Path photo = Files.writeString(album.resolve("beach.jpg"), "x");
deleteTree(account);
boolean accountGone = Files.notExists(account);              // true

The visitor also gives us control over errors. For example, visitFileFailed() can log a file it cannot read and return CONTINUE, so the cleanup removes everything else instead of stopping at the first problem.

3. Symbolic Links Inside the Tree

Both Files.walk() and Files.walkFileTree() leave symbolic links unfollowed unless we ask otherwise, which means a link inside the tree is deleted as a link and its target stays untouched. That default protects us from a link such as shared -> /var/data turning a cache cleanup into the loss of real data.

Path base = Files.createTempDirectory("links");
Path keep = Files.createDirectories(base.resolve("outside"));
Path kept = Files.writeString(keep.resolve("keep.txt"), "important");
Path tmp = Files.createDirectories(base.resolve("tmp"));
Path link = Files.createSymbolicLink(tmp.resolve("shared"), keep);
deleteRecursively(tmp);
boolean tmpGone = Files.notExists(tmp);                       // true
boolean targetSafe = Files.exists(kept);                      // true

We never pass FileVisitOption.FOLLOW_LINKS to a delete walk, and we check how symbolic links behave before writing any custom tree code.

4. Deleting a Single File or an Empty Directory

For one file or an empty folder, no walk is needed. The Files class has two methods, and java.io.File has two older ones that older code still uses.

4.1. Files.delete() and Files.deleteIfExists()

The method Files.delete() removes a file, an empty folder or a symbolic link, and throws an exception that names the reason when it can’t. The method Files.deleteIfExists() does the same, but returns false for a missing path instead of throwing NoSuchFileException.

Path dir = Files.createTempDirectory("docs");
Path draft = Files.writeString(dir.resolve("draft.txt"), "v1");
Files.delete(draft);                                            // removes the file
boolean deletedAgain = Files.deleteIfExists(draft);             // false, already gone
Path ghost = dir.resolve("ghost.txt");
Files.delete(ghost);                                            // NoSuchFileException
Path dir = Files.createTempDirectory("docs");
Path notes = Files.writeString(dir.resolve("notes.txt"), "x");
Files.delete(dir);                                              // DirectoryNotEmptyException

A false result from deleteIfExists() means only that the file was not there. Every real failure, such as a missing permission, still throws. Cleanup code that may run twice, such as a finally block, uses deleteIfExists() so the second run does not fail.

4.2. File.delete() and File.deleteOnExit()

The method File.delete() returns true when it removed the file or empty folder and false for every failure, without a reason. The method deleteOnExit() registers the file for deletion when the JVM shuts down normally, and a registration cannot be cancelled.

File folder = Files.createTempDirectory("old").toFile();
File log = new File(folder, "app.log");
boolean created = log.createNewFile();                          // true
boolean folderDeleted = folder.delete();                        // false, not empty
boolean logDeleted = log.delete();                              // true
boolean nowDeleted = folder.delete();                           // true

We avoid deleteOnExit() in long-running servers, because the JVM keeps every registered path in memory until shutdown, and a crash or kill -9 skips the deletion. Temporary files have better cleanup options, covered in creating a temporary file.

5. Deleting Only the Contents of a Directory

Sometimes the folder must stay. Say an import service watches an incoming folder with a WatchService. Deleting and recreating the folder breaks the watch registration, so the nightly cleanup removes only what is inside. We use the same walk and skip the root path.

static void deleteContents(Path dir) throws IOException {
    try (Stream<Path> tree = Files.walk(dir)) {
        for (Path path : tree.sorted(Comparator.reverseOrder()).toList()) {
            if (!path.equals(dir)) {
                Files.delete(path);
            }
        }
    }
}
Path incoming = Files.createTempDirectory("incoming");
Path batch = Files.createDirectories(incoming.resolve("batch-1"));
Path csv = Files.writeString(batch.resolve("orders.csv"), "x");
deleteContents(incoming);
boolean folderKept = Files.isDirectory(incoming);              // true
long entries = incoming.toFile().list().length;                // 0

To remove the content of a single file instead, i.e. truncate it to zero bytes, see delete the contents of a file.

6. Library Methods for Recursive Delete

Projects that depend on Apache Commons IO or Spring anyway can replace the walk with one call. We don’t add a dependency only for this, because the walk in section 1 is five lines.

  • Commons IO PathUtils.deleteDirectory(path) and FileUtils.deleteDirectory(file) delete a tree and throw IOException on failure. For a missing folder, FileUtils.deleteDirectory() does nothing, whereas PathUtils.deleteDirectory() throws NoSuchFileException.
  • Commons IO FileUtils.cleanDirectory(file) deletes the contents and keeps the folder.
  • Commons IO FileUtils.deleteQuietly(file) never throws and returns false on failure, which hides problems.
  • Spring FileSystemUtils.deleteRecursively(path) returns false for a missing path and throws IOException on failure.
Path logs = Files.createTempDirectory("logs");
Path old = Files.writeString(Files.createDirectories(logs.resolve("2025")).resolve("app.log"), "x");
PathUtils.deleteDirectory(logs);
boolean logsGone = Files.notExists(logs);                       // true

7. Why a Delete Fails

When Files.delete() throws, the exception type points to the cause. The File methods return false for all of these cases, which is why we prefer Files.

ExceptionTypical causeFix
DirectoryNotEmptyExceptionFolder deleted before its content, or another process added a file during the walkDelete children first; stop the writer or retry
NoSuchFileExceptionPath already deleted, often by a concurrent cleanupUse deleteIfExists()
AccessDeniedExceptionNo write permission on the parent folder, or on Windows a read-only fileFix permissions, see read-only files
FileSystemException (file in use)On Windows, another program or our own unclosed stream holds the file openClose streams with try-with-resources

On Linux, deleting a file needs write permission on the folder that contains it, not on the file itself. That is why a read-only file inside a writable folder can still be deleted there.

8. Directory Deletion FAQs

Non-empty folders, the recycle bin, age-based cleanup and a silent false from File.delete() bring most readers to these follow-up questions.

8.1. Can Files.delete() remove a non-empty directory?

No. It throws DirectoryNotEmptyException. We delete the content first with Files.walk() or Files.walkFileTree(), or call a library method such as Commons IO PathUtils.deleteDirectory().

8.2. Can we move files to the recycle bin instead of deleting them?

Yes, on desktops. The method java.awt.Desktop.getDesktop().moveToTrash(file) moves a file or folder to the trash on systems that support it. The Files methods always delete permanently, and servers without a desktop have no trash.

8.3. How do we delete only files older than 30 days?

We walk the folder, keep regular files whose last-modified time is older than the cutoff and delete them.

static void deleteOlderThan(Path dir, Instant cutoff) throws IOException {
    try (Stream<Path> tree = Files.walk(dir)) {
        for (Path file : tree.filter(Files::isRegularFile).toList()) {
            if (Files.getLastModifiedTime(file).toInstant().isBefore(cutoff)) {
                Files.delete(file);
            }
        }
    }
}
Path reports = Files.createTempDirectory("reports");
Path stale = Files.writeString(reports.resolve("q1.pdf"), "x");
Path touched = Files.setLastModifiedTime(stale, FileTime.from(Instant.parse("2026-01-01T00:00:00Z"))); // January
Path fresh = Files.writeString(reports.resolve("q3.pdf"), "x");
deleteOlderThan(reports, Instant.parse("2026-06-01T00:00:00Z"));
boolean staleGone = Files.notExists(stale);                     // true
boolean freshKept = Files.exists(fresh);                        // true

8.4. Why does File.delete() return false?

The file is missing, it is a folder that still has content, the process lacks permission, or on Windows the file is open. File.delete() does not say which, so we call Files.delete(file.toPath()) to get an exception with the cause.

9. Conclusion

Files.walk() sorted in reverse order, with Files.delete() on each path, deletes a directory recursively in a few lines and reports every failure. For very large trees, Files.walkFileTree() deletes while it walks, and both approaches leave the targets of symbolic links alone.

For single files and empty folders, Files.delete() explains failures with an exception and Files.deleteIfExists() tolerates a missing path. We skip the boolean File methods in new code and add Commons IO or Spring only when the project already has them. The Java IO tutorial covers the related file tasks.

10. References

Happy Learning !!

Source Code on Github

Leave a Comment

    • “No exceptions are thrown when a file or directory cannot be deleted”. This may not be the desired behavior in many cases. Otherwise it is also doing the same as above code i.e. first delete all containing files and then delete the directory itself.

      public static boolean deleteQuietly(final File file) {
      	if (file == null) {
      		return false;
      	}
      	try {
      		if (file.isDirectory()) {
      			cleanDirectory(file);
      		}
      	} catch (final Exception ignored) {
      	}
      
      	try {
      		return file.delete();
      	} catch (final Exception ignored) {
      		return false;
      	}
      }

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.