Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
207: Watching Directories with WatchService Revisited
Why am I getting multiple MODIFY events for a single file save?
This is probably the first thing that drove me crazy when I started using WatchService. You save a file in your IDE once, and your console screams that the file was modified three times in a row. It feels like a bug in Java, but it's actually how the operating system and your text editor interact.
Most modern editors don't just overwrite a file. They create a temporary file, write the changes there, delete the original, and then rename the temporary file. To the WatchService, that's a flurry of activity. Even a simple save can trigger a CREATE, a MODIFY, and then another MODIFY as the OS updates the file's metadata (like the "last modified" timestamp).
I usually handle this by implementing a simple "cooldown" or debouncing mechanism. Instead of reacting immediately, I store the filename and the timestamp of the event in a map. If another event for the same file arrives within, say, 100 milliseconds, I ignore it. Here is the conceptual approach:
Map<Path, Long> lastEventTime = new HashMap<>();
long cooldown = 100; // milliseconds
while (true) {
WatchKey key = watcher.take();
for (WatchEvent<Path> event : key.pollEvents()) {
Path fileName = event.context();
long now = System.currentTimeMillis();
if (now - lastEventTime.getOrDefault(fileName, 0L) > cooldown) {
System.out.println("Actually processing change for: " + fileName);
lastEventTime.put(fileName, now);
// Do the heavy lifting here
}
}
key.reset();
}
What happens when I see an OVERFLOW event?
If you're monitoring a directory where thousands of files are being dumped in a matter of seconds—maybe a log directory or a temporary build folder—you'll eventually encounter StandardWatchEventKinds.OVERFLOW. This is the WatchService basically admitting, "I can't keep up; I've dropped some events."
You can't "recover" the missed events because they're gone from the buffer. When I hit an overflow, the only safe move is to trigger a full manual scan of the directory. I usually write a method that lists all files in the watched folder and compares them against my internal state to find out what changed while the service was overwhelmed. Don't try to guess what you missed; just re-sync everything.
How do I make the watcher recursive?
Here is the frustrating part: WatchService is not recursive. If you register /my-project, you'll hear about files added to that folder, but you'll be completely deaf to anything happening in /my-project/src/main/java. I've seen a lot of developers assume this works out of the box, only to spend hours wondering why their subfolders are ignored.
To fix this, you have to manually register every single subdirectory. The cleanest way to do this is using Files.walkFileTree. You walk the tree, find every directory, and call register() on each one. Just keep in mind that if a new directory is created while the program is running, you need to register that new directory on the fly, or it will also be ignored.
Files.walkFileTree(startPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); return FileVisitResult.CONTINUE; } });I'll give you a warning: be careful with this on massive file systems. Every registered directory consumes OS resources. If you try to watch a root drive with 100,000 folders, you might hit an OS limit or tank your performance.
📋 Practical Task
Exercise: Building a Hot-Reloading Config Monitor
Create a program that monitors a specific folder named /config for changes to a file named settings.properties. The program should:
- Print "Reloading configuration..." whenever the file is modified.
- Implement a 200ms cooldown (debounce) mechanism to ensure that a single save operation doesn't trigger the "Reloading" message multiple times.
- Gracefully handle the
OVERFLOWevent by printing a warning that a manual resync is required. - Run in a separate thread so that the main application remains responsive.
There are no comments for now.