Polling WatchService for file modifications

On the previous page, we explained how to set up a WatchService to listen for file modifications. Now we are ready to actually poll the WatchService for those modifications. Polling for modifications happens in the following stages:

In a typical application, this whole process will take place in a loop in a separate thread. If you are not familiar with threading in Java, then you may wish to take the opportunity to read up on this topic. We will provide some very simple boilerplate code below as an example of watching in a separate thread and will mention the main threading issues you need to be aware of.

Polling WatchService

Our outer loop consists of a repeated call to poll the watchService for the next key (directory) for which there are pending notification events. The basic loop typically looks as follows:

public class MyWatcher {
  private final WatchService watcher = ...set up as above...

  private void processFileNotifications() throws InterruptedException {
    while (true) {
      WatchKey key = watcher.take();
      // process events for this "key" (directory)
    }
  }
}

Note that this loop will not consume CPU while there are no pending notifications. Instead, the take() method will block, meaning that the thread will sleep pending any incoming notification. In other words, WatchService works very much like a Java BlockingQueue.

The take() method and its blocking behaviour are usually what is required. If you need them, WatchService also provides methods allowing you to poll for events with a timeout, or poll() returning immediately with whether or not events are available. If you use the latter functionality, it is up to your application to ensure that it doesn't call WatchService.poll() continuously and hence burn CPU.

From this example, you will notice that we store a reference to our WatchService in a final variable for thread-safety: typically, the service will be set up in one thread but then processFileNotifications() called from a different thread.

Retrieving individual modification notifications

Whenever the watcher.take() "wakes up", we need to then poll the WatchKey passed to us for the actual modification notifications. To this end, we call the WatchKey's pollEvents() method. This returns a list of WatchEvent objects which we can cycle through:

  private void processFileNotifications() throws InterruptedException {
    while (true) {
      WatchKey key = watcher.take();
      Path dir = keyPaths.get(key);
      for (WatchEvent evt : key.pollEvents()) {
        WatchEvent.Kind eventType = evt.kind();
	if (eventType == OVERFLOW)
          continue;
        Object o = evt.context();
        if (o instanceof Path) {
          Path path = (Path) o;
          process(dir, path, eventType);
        }
      }
      key.reset();
    }
  }

  private void process(Path dir, Path file, WatchEvent.Kind evtType) {
    ...
  }

The first thing we do is query our previously-created keyPaths map to find out which directory corresponds to this key. Notifications are sent to us in terms of relative paths, so if we want to know the parent directory, we must record it explicitly at the time of registering for notifications as in our example above.

We then check the event type, represented by a WatchEvent.Kind object. This could be one of the three event types registered for— CREATION, DELETION, MODIFICATION— or the special OVERFLOW type. An OVERFLOW event in effect means that the WatcherService received so many notifications from the filing system that it couldn't process them all in time. It tells our application that "you've missed some events", but what we do about this in practice will depend on our application. Here, we simply skip passed other events returned to us: the logic being that if we know we've missed some events and cannot keep up, we may as well skip the current queue and start polling again in an attempt to catch up with more recent events. An alternative procedure for some applications might be to decide not to re-queue the key (see below) after a certain number of overflows are received.

Finally, we pull out the Path indicating the file that has been modified, then call our own process() method with the details.

Re-queueing the watch key

Notice the call to key.reset(): this effectively means "I want to go on receiving modification notifications for this key/directory".

Putting it together: threading

A final stage of implementation will usually be to listen for notifications in a separate thread.


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.