Close

Java NIO - Watching a directory for changes

[Last Updated: Jun 7, 2017]

The java.nio.file provides file/directory change notification mechanism. Followings are the steps to accomplish that.


  1. Getting an instance of WatchService

      WatchService watchService = FileSystems.getDefault().newWatchService() 

    A single instance of WatchService is used to register multiple directories/subdirectories. Doing so enable us to receive external changes (create/delete/rename/modify) to those directories and the files which reside under them.


  2. Registering a directory with the WatchService

    The instances of Watchable interface can register() themselves to WatchService, e.g. Path implements Watchable:

      Path path = Paths.get("d:\\testDir");
      WatchKey key = path.register(watchService,
                                        StandardWatchEventKinds.ENTRY_CREATE,
                                        StandardWatchEventKinds.ENTRY_MODIFY,
                                        StandardWatchEventKinds.ENTRY_DELETE);
      
    • The second vararg argument of register() method, StandardWatchEventKinds are WatchEvent.Kind type constants for file/folder create/modify/delete events. We can choose more or less of these constants depending on our needs.

    • The return value WatchKey is a token which has internal life-cycle states at difference stages:
      Ready state: when WatchKey is created it's in 'ready' state. It can also be in ready state on calling WatchKey#reset() method.
      Queued: when a file/folder event occurred, it is 'signalled' and 'queued', and can be retrieved by using WatchKey#pollEvents() method which returns a list of WatchEvent.

    • Once signalled, the WatchKey#reset() method should be called so that the key will return to the 'ready' state.

      Note that the return 'key' from above method is not actually used for polling, instead we use WatchService#take() method to return a 'queued' key for efficiency (see next).



  3. Polling events

    while (true) {
        WatchKey queuedKey = watchService.take();
        for (WatchEvent<?> watchEvent : queuedKey.pollEvents()) {
            System.out.printf("kind=%s, count=%d, context=%s Context type=%s%n ",
                                watchEvent.kind(),
                                watchEvent.count(), watchEvent.context(),
                                ((Path) watchEvent.context()).getClass());
          //do something useful with the modified file/folder here
    
            if (!queuedKey.reset()) {
                break;
            }
        }
    }
    

    WatchService#take() method blocks and waits until one or more keys are available in queue due to external changes. The returned key can be any of the directories registered with the service.

    The returned 'queuedKey' can be used to WatchKey#pollEvents().

    Note that calling WatchKey#pollEvents() on the key return in step 2 above will not block and the 'while' loop will be constantly iterating which is not efficient at all, we should always use WatchService#take() method instead.



In above example creating/renaming/deleting files/directories will queue the WatchEvents. Here's the output on creating a new folder under 'd:\\testDir':

kind=ENTRY_CREATE, count=1, context=New folder Context type=class sun.nio.fs.WindowsPath

In the output, sun.nio.fs.WindowsPath is the implementation of Path for Windows operating system.



Recursively watching directories

Above example doesn't watch sub-directories under the main registered directory. Following is the complete code to achieve that:

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;


public class WatchServiceRecursiveExample {
   private static Map<WatchKey, Path> keyPathMap = new HashMap<>();

   public static void main (String[] args) throws Exception {
       try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
           registerDir(Paths.get("d:\\testDir"), watchService);
           startListening(watchService);
       }
   }

   private static void registerDir (Path path, WatchService watchService) throws
                       IOException {


       if (!Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) {
           return;
       }

       System.out.println("registering: " + path);


       WatchKey key = path.register(watchService,
                           StandardWatchEventKinds.ENTRY_CREATE,
                           StandardWatchEventKinds.ENTRY_MODIFY,
                           StandardWatchEventKinds.ENTRY_DELETE);
       keyPathMap.put(key, path);


       for (File f : path.toFile().listFiles()) {
           registerDir(f.toPath(), watchService);
       }
   }

   private static void startListening (WatchService watchService) throws Exception {
       while (true) {
           WatchKey queuedKey = watchService.take();
           for (WatchEvent<?> watchEvent : queuedKey.pollEvents()) {
               System.out.printf("Event... kind=%s, count=%d, context=%s Context type=%s%n",
                                   watchEvent.kind(),
                                   watchEvent.count(), watchEvent.context(),
                                   ((Path) watchEvent.context()).getClass());

               //do something useful here

               if (watchEvent.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                   //this is not a complete path
                   Path path = (Path) watchEvent.context();
                   //need to get parent path
                   Path parentPath = keyPathMap.get(queuedKey);
                   //get complete path
                   path = parentPath.resolve(path);

                   registerDir(path, watchService);
               }
           }
           if(!queuedKey.reset()){
               keyPathMap.remove(queuedKey);
           }
           if(keyPathMap.isEmpty()){
               break;
           }
       }
   }
}

Example Project

Dependencies and Technologies Used:

  • JDK 1.8
  • Maven 3.0.4

Watch Service Examples Select All Download
  • nio-watch-service
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • WatchServiceExample.java

    See Also