Java Basics: The Watch Service API

Using the Watch Service API to Watch for File System Changes

In this next installment of the Java Basics series, we’ll keep going with File I/O theme.  This is a follow on to recent Java Basics posts on Java Basics: File I/OJava Basics: NIO Files and Java Basics: Custom File Filter.

The Watch Service API was introduced in Java 7 and allows us to register any file system resource that implements the Watchable interface and receive notifications for changes.  We’ll be using a Path in our example.

We start by registering a WatchService on our file system.  Then we get a specific WatchKey for our resource.  When we register our WatchKey, we specify the types of StandardWatchEventKinds we want to monitor:

  • ENTRY_CREATE – a resource was created in our monitored directory
  • ENTRY_MODIFY – an entry was modified
  • ENTRY_DELETE – a resource was deleted
  • OVERFLOW – we don’t need to explicitly monitor for this kind which occurs when events may have been list or discarded.
WatchService watcher = FileSystems.getDefault().newWatchService();
Path watchDir = FileSystems.getDefault().getPath(WATCHED_DIR);
WatchKey watchedKey = watchDir.register(watcher, 
   StandardWatchEventKinds.ENTRY_CREATE,
   StandardWatchEventKinds.ENTRY_MODIFY, 
   StandardWatchEventKinds.ENTRY_DELETE);

Once we’ve got our WatchService set up, we use one of its three methods to check for events:

  • poll – retrieves (and removes) the next watch key.  Returns null if none available
  • poll(timeout, TimeUnit) – like poll, but waits until the timeout is reached.  Units of timeout are specified by the TimeUnit.
  • take – retrieves and removes the next key, but unlike poll, it waits if none are available.

Let’s have a look at using the poll method now.  We enter a loop and poll our watcher.  If nothing has happened, our WatchKey is null and we just keep looping.  If we’ve got something, we poll its events and process them depending on their kind.

For our event, we can get the Path out of the context.  Once we have our Path, we can find things out about the file such as it’s name and content type.  For this example, we’re just printing out some information about what’s going on, but in a real application we might spin off another thread to do some processing on the file or fire off a notification of some type.

At the end, we reset the event WatchKey and if it’s no longer valid we break out of our loop.

while (true) {
   WatchKey eventKey = watcher.poll();
   if (eventKey != null) {
      for (WatchEvent<?> event : eventKey.pollEvents()) {

         if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
            System.out.println("Possible data loss!");
         }

         WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
         Path filename = pathEvent.context();

         if (pathEvent.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
            try {
               System.out.printf("File '%s' created.\n", filename.getFileName());
               System.out.printf("File type = %s\n", Files.probeContentType(filename));
               System.out.printf("File size = %d\n", Files.size(filename));
            } catch (NoSuchFileException e) {
               continue;
            }
         } else if (pathEvent.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
            try {
               System.out.printf("File '%s' modified.\n", filename.getFileName());
               System.out.printf("File type = %s\n", Files.probeContentType(filename));
            } catch (NoSuchFileException e) {
               continue;
            }
         } else if (pathEvent.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
            System.out.printf("File '%s' deleted.\n", filename.getFileName());
         }
      }

      boolean valid = eventKey.reset();
      if (!valid) {
         break;
      }
   }
}

Conclusion

By using the Watch Service API that was introduced in Java 7, we’re able to detect changes to a directory on the local file system.  We see this kind of thing in action when our IDE detects and brings in changes automatically.  Another possible use could be for a program that needs to process some file that another process places in a particular directory.

In this post, we only looked at the poll method, but the example code over on GitHub shows using the poll method with timeout and the take method.