1. Introduction
When you work with files and directories, you often need to select only certain files: for example, all ".java" files, all images, or all logs for a specific date. To do this in Java (and elsewhere), people use globbing (glob) and regular expressions (regex).
- Globbing — a simple way to describe a file name pattern using special characters (*, ?, [], {}), like in a Linux or Windows command line.
- Regex — a powerful regular-expression language for complex patterns.
Examples of globbing patterns
- *.java — all files with the ".java" extension in the current directory.
- **/*.java — all ".java" files in all subdirectories (double asterisk — recursive search).
- *.{png,jpg} — all files with the ".png" or ".jpg" extension.
- file-??.log — files like "file-01.log", "file-AB.log" (any two characters).
- [A-Z]*.txt — all ".txt" files that start with an uppercase letter.
Globbing is simpler than regex, and most of the time it’s enough for file filtering.
Glob vs. regex
| Feature | glob | regex |
|---|---|---|
| Simplicity | Very simple | More complex but more powerful |
| Symbols | |
Full regex capabilities |
| Examples | |
|
| Recursion | |
No built-in support |
| When to use | File filtering | Complex checks |
2. PathMatcher: filtering files by pattern
In Java NIO (java.nio.file), to filter files by a pattern use the PathMatcher interface. You can obtain it via FileSystems.getDefault().getPathMatcher(...).
Syntax
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
- "glob:*.java" — a globbing pattern.
- "regex:.*\\.java" — a regular expression pattern.
Example: filtering files in a directory
import java.nio.file.*;
Path dir = Paths.get("src");
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry : stream) {
if (matcher.matches(entry.getFileName())) {
System.out.println(entry);
}
}
}
Important:
- matcher.matches() usually checks only the file name — pass entry.getFileName(), not the full path.
- For recursive search, use Files.walk() or Files.find().
Files.walk() is an NIO2 method that returns a Stream<Path> with all files and directories in the specified directory recursively, including subdirectories. Unlike DirectoryStream, which shows only the contents of a single directory, Files.walk() lets you work with the directory tree via the Stream API.
Usage example:
import java.nio.file.*;
import java.util.stream.Stream;
Path start = Paths.get("src");
try (Stream<Path> stream = Files.walk(start)) { // all subdirectories recursively
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
Example with regex
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("regex:.*\\.(png|jpg)");
3. Files.newDirectoryStream: filtering while listing a directory
The Files.newDirectoryStream() method lets you filter files by pattern or with a custom filter right away.
Filtering by a glob pattern
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
System.out.println(entry);
}
}
The second parameter is a glob pattern (without the "glob:" prefix).
Filtering with DirectoryStream.Filter
If you need more complex logic (for example, filtering by size, date, or excluding directories), use DirectoryStream.Filter<Path>:
DirectoryStream.Filter<Path> filter = path -> {
// Example: files only (no directories), and not .git or node_modules
return Files.isRegularFile(path)
&& !path.getFileName().toString().equals(".git")
&& !path.getFileName().toString().equals("node_modules");
};
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
for (Path entry : stream) {
System.out.println(entry);
}
}
4. Files.find: powerful search with BiPredicate
If you need to search for files by complex conditions (for example, by date, size, name, recursively), use Files.find.
Syntax
Stream<Path> stream = Files.find(
startDir, // where to search
maxDepth, // depth (Integer.MAX_VALUE — recursive)
(path, attrs) -> {
// path — path to the file
// attrs — file attributes (size, date, etc.)
return path.getFileName().toString().endsWith(".log")
&& attrs.size() > 1024; // only large logs
}
);
stream.forEach(System.out::println);
- The second parameter is the maximum search depth.
- The third is BiPredicate<Path, BasicFileAttributes>: returns true if the file matches.
Example: exclude .git and node_modules directories
Stream<Path> stream = Files.find(
Paths.get("."),
Integer.MAX_VALUE,
(path, attrs) -> {
String name = path.getFileName().toString();
// Exclude .git and node_modules directories
if (name.equals(".git") || name.equals("node_modules")) return false;
// Only .log files larger than 1 MB
return name.endsWith(".log") && attrs.size() > 1024 * 1024;
}
);
stream.forEach(System.out::println);
5. Practice
Example 1: Find all .log files except .git and node_modules
Files.walk(Paths.get("."))
.filter(path -> {
String name = path.getFileName().toString();
// Exclude directories
if (name.equals(".git") || name.equals("node_modules")) return false;
// Only .log files
return name.endsWith(".log");
})
.forEach(System.out::println);
Example 2: Find all images created after a specific date
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
Instant after = Instant.parse("2024-06-01T00:00:00Z");
Files.find(
Paths.get("images"),
Integer.MAX_VALUE,
(path, attrs) -> {
String name = path.getFileName().toString();
return (name.endsWith(".png") || name.endsWith(".jpg"))
&& attrs.creationTime().toInstant().isAfter(after);
}
).forEach(System.out::println);
Example 3: Find all large files (more than 10 MB) except .git
Files.find(
Paths.get("."),
Integer.MAX_VALUE,
(path, attrs) -> {
String name = path.getFileName().toString();
return !name.equals(".git") && attrs.size() > 10 * 1024 * 1024;
}
).forEach(System.out::println);
6. Useful nuances
Quick cheat sheet for glob syntax
- * — any number of any characters (except the separator /)
- ** — any number of any directories (works in Java)
- ? — exactly one character
- [abc] — any of the characters a, b, c
- [a-z] — any character from the range
- {a,b,c} — any of the listed values (for example, *.{png,jpg})
Examples:
- *.java — all ".java" files in the current directory
- **/*.java — all ".java" files in all subdirectories
- file-??.log — files like "file-01.log", "file-AB.log"
- [A-Z]*.txt — all ".txt" files that start with an uppercase letter
Performance and leaks: close DirectoryStream!
Important!
- DirectoryStream and the streams from Files.find/Files.walk are resources that must be closed.
- Use try-with-resources:
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
// ...
}
}
- If you don’t close the stream, resource leaks may occur (for example, “too many open files”).
- For Files.find and Files.walk, make sure to call close() or use try-with-resources:
try (Stream<Path> stream = Files.find(...)) {
stream.forEach(System.out::println);
}
7. Summary and common mistakes
Error #1: Using a glob pattern without understanding that * is not recursive. For recursion, use "**/*.java" or Files.walk.
Error #2: Passing the full path to matcher.matches() — you usually need to pass only the file name (getFileName()).
Error #3: Forgetting to close DirectoryStream or Stream<Path> — you’ll get a resource leak.
Error #4: Overly complex regex patterns for simple filtering — use glob if it’s sufficient.
Error #5: Not excluding special directories (".git", "node_modules") — the search becomes slow and cluttered with unnecessary files.
GO TO FULL VERSION