Introduction to Java WatchService
The Java NIO.2 API includes the WatchService
framework for monitoring changes to directories in real time. In modern operating systems, it is common for two processes to access the same files in the same directory during their execution. If we expect a directory or its contents to be changed over time by another process, we can use the WatchService
API to monitor the directory for changes and react to those changes as soon as they occur.
Applying the WatchService
API to monitor a directory requires a number of steps. The following is a high-level overview of the WatchService
process:
- Create an instance of WatchService from the file system.
- Register each directory and event type.
- Create a loop that repeatedly queries the WatchService for changes.
- Retrieve a WatchKey.
- Retrieve all pending events for the WatchKey and do something with them.
- Reset the WatchKey.
- Once you no longer need the WatchService, close the resource.
Creating and Shutting Down the WatchService
The first step is the easiest. We use the FileSystems
helper class to obtain a reference to the default FileSystem
. We then use the FileSystem
object to obtain a new WatchService
instance, as shown in the following code snippet:
WatchService service = FileSystems.getDefault().newWatchService();
If the WatchService
is being created and closed within a single method, we can apply the try-with-resource syntax, as WatchService
extends Closeable
in order to complete the first and last steps in an abridged fashion:
import java.io.IOException;
import java.nio.file.*;
public class WatchServiceSample {
public static void main(String[] args) throws IOException {
try (WatchService service = FileSystems.getDefault().newWatchService()) {
...
}
}
}
Alternatively, if we are not creating and closing the WatchService
instance within a single method, we need to explicitly call the close()
method on the WatchService
instance after we have finished using it. Failure to close the WatchService
after we have finished with it could lead to resource-contention issues within the file system.
Different Events to monitor with Java Watcher
Next we should know the list of events which we would like to monitor. The WatchService
can be used on any class that implements the Watchable interface, which requires the class to implement register()
methods. In the NIO.2 API, the Path interface extends the Watchable interface; therefore we can use our WatchService
instance to monitor any number of Path objects by calling a register()
method.
Along with the WatchService
instance, the register()
method takes a vararg
of StandardWatchEventKinds
enum
values, which indicates the events for which we want to listen.
The WatchService
API supports the four event types listed in below table:
Enum Value | Description |
StandardWatchEventKinds.ENTRY_CREATE |
An element is added to the directory. |
StandardWatchEventKinds.ENTRY_DELETE |
An element is removed from the directory. |
StandardWatchEventKinds.ENTRY_MODIFY |
An existing element is modified in the directory. |
StandardWatchEventKinds.OVERFLOW |
An event may have been lost. It is possible to receive this event even if it is not registered for. |
How to create a Java Watcher?
The java watcher program will use java watchService API where you first register the directory to be watched. And then the events that you want to be notified for has to be registered into the program.
- Create an instance of the watchService
WatchService watcher = pathDirectory.getFileSystem().newWatchService();
- Register a path using the nio.path Path
pathDirectory = Paths.get("your directory path");
- Register the events that you want to be notified for
StandardWatchEventKinds.ENTRY_CREATE
,StandardWatchEventKinds.ENTRY_DELETE
,StandardWatchEventKinds.ENTRY_MODIFY
- Create a java watch key.
- Create a java list in which the events occurring can be stored.
- Record the file name
- Check if it is updated, deleted or modified.
- Show the results.
Java WatchService Example
Monitor single directory
import java.util.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
public class javaWatcher {
public static void main(String[] args) {
// Java watcher : register the directory to be watched.
Path pathDirectory = Paths.get("C:/Users/Azka/Desktop/assignment 1");
while (true) {
try {
// java watcher : create an instance of the watch Service
WatchService watcher = pathDirectory.getFileSystem().newWatchService();
// java watcher : register the events that are to be notified.
pathDirectory.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,StandardWatchEventKinds.ENTRY_MODIFY);
// java watcher : watch key
WatchKey watchKey = watcher.take();
// java watcher : enter the events into a list
List<WatchEvent<?>> eventsList = watchKey.pollEvents();
// for all events create a loop that iterates till the end of the
// list
for (WatchEvent event : eventsList) {
// get the file name for the event
Path fileWatched = (Path) event.context();
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
//file is created
System.out.println("File created: " + fileWatched);
}
// file is deleted
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
System.out.println("File deleted: " + fileWatched);
}
// file is modified.
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println("File modified: " + fileWatched);
}
}
} catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
}
}
In my case the directory contains the following content
Now lets change the content of the file “average.txt” that originally contains the following content
Now lets change it to
Lets see what is the output of the code
File modified: average.txt
Similarly if I delete a file, the output becomes
File modified: average.txt File deleted: best.txt
All these actions are performed and updated on runtime which our Java WatchService was able to monitor and report.
Monitor sub-directories recursively
WatchService only watches the files and directories immediately beneath it. What if we want to watch to see if either p.txt
or c.txt
is modified?
/dir
| - parent
| - p.txt
| - child
| - c.txt
One way is to register both directories:
WatchService watcher = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("/dir/parent")
dir.register(watcher, ENTRY_MODIFY);
Patch child = Paths.get("dir/parent/child");
child.register(watcher, ENTRY_MODIFY);
This works. You can type in all the directories you want to watch. If we had a lot of child directories, this would quickly get to be too much work. If we need to iterate through a whole directory hierarchy instead of just a single directory, we can use a FileVisitor
. The FileswalkFileTree()
method takes a starting path and performs a depth-first traversal of the file hierarchy, giving the provided FileVisitor
a chance to “visit
” each path element in the tree.
Path myDir = Paths.get("/dir/parent");
final WatchService watcher = FileSystems.getDefault().newWatchService();
Files.walkFileTree(myDir, new SimpleFileVisitor<Path> () {
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
dir.register(watcher, ENTRY_MODIFY);
return FileVisitResult.CONTINUE;
}
});
This code goes through the file tree recursively registering each directory with the watcher.
Java WatchService Functions
Java WatchService have some important methods
Modifier and Type | Method and Description |
Void close() | Closes the service |
WatchKey poll() | Retrieves and removes the next watch key |
WatchKey take() | Retrieves and removes next watch key, waiting if none are yet present. |
Limitations of Java WatchService
- Even though the WatchService API allows us to monitor a directory for changes, it does so with a number of known drawbacks. First off, it is possible to miss directory change events, you can add code to check whether
kind == OVERFLOW
, but that just tells you something went wrong. You don’t know what events you lost. - Second, when events are lost, we do not get any information about the lost events, other than we know that something was lost. Receiving no information about precisely which events were lost might make some people refrain from using the
WatchService
API altogether. - Finally, some JVMs implementations of the
WatchService
API are inefficient, with significant delays between the time that the directory is modified and the moment that the application is notified about the change. Some developers have even reported delays of up to five seconds. This may not seem like a significant amount of time to you, but for someone writing an application that continuously monitors a directory for changes, this may have a drastic impact on their application.
Conclusion
We have studied how a program can be a java watcher program and can be notified about any changes in a directory. The algorithm is further discussed, we can create an instance and register a path, after that we define the events that are to be notified within a directory, a list maintains these events and can be shown on the terminal or front-end of the program, this service helps a lot for security purposes, any changes in files will be notified on runtime.
Further Reading