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
96: Watching the Filesystem with WatchService
A few years back, I was working on a legacy system that relied on a massive XML configuration file. The problem was that the app took nearly two minutes to boot up, but we needed to tweak settings frequently during deployment. To avoid that two-minute wait, my teammate tried to implement a "hot-reload" feature by writing a background thread that checked the file's timestamp every five seconds. It worked, but it was clumsy. On a network drive, that constant polling created a noticeable amount of chatter and unnecessary I/O overhead. I remember telling him, "Why are we asking the filesystem if it's changed every five seconds when the operating system can just tell us the moment it happens?"
That's where WatchService comes in. Part of the java.nio.file package, the WatchService is essentially a registration desk. You tell the OS which directory you're interested in and what specific events you care about—like a file being created, modified, or deleted—and then your program just sits back and waits for a notification. It's significantly more efficient than polling because it leverages native OS capabilities (like inotify on Linux or ReadDirectoryChangesW on Windows).
Registering for Events
To get this working, you first need to create a WatchService from the filesystem. Once you have that, you take a Path object for the directory you want to monitor and register it. When you register, you pass in a set of StandardWatchEventKinds. Most of the time, you'll want ENTRY_CREATE, ENTRY_DELETE, and ENTRY_MODIFY.
import java.nio.file.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class FolderWatcher {
public static void main(String[] args) throws IOException, InterruptedException {
WatchService watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get("C:/my-configs"); // Use a path that exists on your machine
// Register the directory for create, delete, and modify events
path.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Watching directory: " + path);
// Now we enter the loop...
}
}
The Event Loop and the Reset Trap
The WatchService doesn't push events to you via a callback; instead, it queues them. You have to poll the service for WatchKey objects. I've seen plenty of developers get stuck here because they forget one crucial detail: WatchKey.reset(). If you don't call reset() on the key after processing the events, the key remains in a "signaled" state and will never trigger again for that directory.
You'll typically use a while(true) loop and call watchService.take(), which blocks the thread until an event actually occurs. If you don't want your thread to hang forever, you can use poll(long timeout, TimeUnit unit) instead. Here is how the actual processing loop looks:
while (true) {
WatchKey key = watchService.take(); // Wait for an event
for (WatchEvent< Path> event : key.pollEvents()) {
WatchEvent.Kind< Path> kind = event.kind();
Path fileName = event.context();
System.out.println(kind.name() + " happened to file: " + fileName);
}
// CRITICAL: Reset the key to receive further events
boolean valid = key.reset();
if (!valid) {
System.out.println("Directory no longer accessible.");
break;
}
}
One little quirk to keep in mind: some editors (like IntelliJ or VS Code) don't just "modify" a file. They often create a temporary file, delete the original, and rename the temp file to the original name. This means a single "Save" action in your editor might trigger a flurry of ENTRY_CREATE and ENTRY_MODIFY events. If you're building a production system, you'll likely want to add a tiny debounce delay to avoid reloading your config five times in ten milliseconds.
📋 Practical Task
Build a Real-time Log Monitor
Create a Java application that monitors a specific folder named logs/. Your application should start a background thread using WatchService that monitors for new files. Whenever a file ending in .log is created or modified in that folder, the program should print a message to the console saying: "Update detected in [filename] - triggering log analysis...". Ensure your code correctly handles the WatchKey reset logic and gracefully exits if the directory is deleted or becomes inaccessible.
There are no comments for now.