Java – List All Files and Subdirectories Recursively
Walking a directory tree is a common task when building file management tools, log analyzers, or build scripts. Java provides multiple ways to list all files and subdirectories including nested ones. This tutorial covers the classic File API, the modern Files.walk() with Stream API (Java 7+), and how to filter by extension.
Method 1: Recursive File.listFiles() (Classic)
package com.java9r;
import java.io.File;
/**
* Recursively lists all files and subdirectories using the classic File API.
*/
public class ListFilesRecursive {
public static void main(String[] args) {
String rootDir = "C:/Projects"; // change to your path
File root = new File(rootDir);
if (!root.exists()) {
System.out.println("Directory not found: " + rootDir);
return;
}
System.out.println("Files and directories in: " + rootDir);
listAll(root, 0);
}
private static void listAll(File dir, int depth) {
File[] items = dir.listFiles();
if (items == null) return;
for (File item : items) {
// Indent based on depth for visual hierarchy
String indent = " ".repeat(depth);
if (item.isDirectory()) {
System.out.println(indent + "[DIR] " + item.getName());
listAll(item, depth + 1); // recurse into subdirectory
} else {
System.out.printf("%s[FILE] %-30s (%,d bytes)%n",
indent, item.getName(), item.length());
}
}
}
}
Method 2: Files.walk() with Stream API (Modern – Java 7+)
package com.java9r;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
/**
* Modern approach using Files.walk() – cleaner, handles IOException properly.
*/
public class ListFilesStream {
public static void main(String[] args) throws IOException {
Path rootPath = Paths.get("C:/Projects");
System.out.println("=== All Files and Directories ===");
try (var stream = Files.walk(rootPath)) {
stream.forEach(path -> {
var depth = rootPath.relativize(path).getNameCount() - 1;
var indent = " ".repeat(depth);
if (Files.isDirectory(path)) {
System.out.println(indent + "[DIR] " + path.getFileName());
} else {
System.out.println(indent + "[FILE] " + path.getFileName());
}
});
}
}
}
Method 3: Filter by File Extension
import java.io.IOException;
import java.nio.file.*;
import java.util.stream.Collectors;
public class FindJavaFiles {
public static void main(String[] args) throws IOException {
Path root = Paths.get("C:/Projects");
// Find all .java files recursively
System.out.println("=== All .java files ===");
try (var stream = Files.walk(root)) {
var javaFiles = stream
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.sorted()
.collect(Collectors.toList());
javaFiles.forEach(p -> System.out.println(" " + p));
System.out.println("\nTotal .java files: " + javaFiles.size());
}
// Count files by extension
System.out.println("\n=== File count by extension ===");
try (var stream = Files.walk(root)) {
stream.filter(Files::isRegularFile)
.collect(Collectors.groupingBy(p -> {
String name = p.getFileName().toString();
int dot = name.lastIndexOf('.');
return dot >= 0 ? name.substring(dot) : "(no ext)";
}, Collectors.counting()))
.entrySet().stream()
.sorted(java.util.Map.Entry.comparingByValue(java.util.Comparator.reverseOrder()))
.forEach(e -> System.out.println(" " + e.getKey() + " : " + e.getValue()));
}
}
}
Method 4: Files.find() — With Attribute Filtering
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class FindLargeFiles {
public static void main(String[] args) throws IOException {
Path root = Paths.get("C:/Projects");
long minSize = 100 * 1024; // 100 KB
System.out.println("Files larger than 100 KB:");
try (var stream = Files.find(root, Integer.MAX_VALUE,
(path, attrs) -> attrs.isRegularFile() && attrs.size() > minSize)) {
stream.sorted()
.forEach(p -> {
try {
long size = Files.size(p);
System.out.printf(" %-50s %,7d KB%n",
p.getFileName(), size / 1024);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
}
Practical Example: Count Lines in All Java Files
import java.io.IOException;
import java.nio.file.*;
import java.util.concurrent.atomic.AtomicLong;
public class CountCodeLines {
public static void main(String[] args) throws IOException {
Path root = Paths.get("C:/Projects/MyApp/src");
AtomicLong totalLines = new AtomicLong(0);
AtomicLong totalFiles = new AtomicLong(0);
try (var stream = Files.walk(root)) {
stream.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(p -> {
try {
long lines = Files.lines(p).count();
totalLines.addAndGet(lines);
totalFiles.incrementAndGet();
System.out.printf(" %4d lines %s%n", lines, p.getFileName());
} catch (IOException e) {
System.err.println("Error reading: " + p);
}
});
}
System.out.println("\nTotal files: " + totalFiles.get());
System.out.println("Total lines: " + totalLines.get());
}
}
File vs Path – Which to Use?
| API | Available Since | Recommendation |
|---|---|---|
| java.io.File | Java 1.0 | Legacy – works but less powerful |
| java.nio.file.Path + Files | Java 7 | Preferred – better error handling, Stream support |
| Files.walk() | Java 7 | Use for directory traversal |
| Files.find() | Java 7 | Use when filtering by file attributes |
Summary
For listing files recursively, prefer the modern Files.walk() over the classic recursive File.listFiles() approach. Files.walk() returns a Stream, which integrates naturally with filtering, sorting, and collecting. Always close the stream with try-with-resources — leaving it open causes a file handle leak. Use Files.find() when you need to filter by file attributes like size or creation date. For large directory trees, the Stream approach is lazily evaluated and memory-efficient.
Comments