{{announcement.body}}
{{announcement.title}}

Watching Files With Java NIO

DZone 's Guide to

Watching Files With Java NIO

Learn more about watching files with Java NIO.

· Java Zone ·
Free Resource

Learn more about watching files with Java NIO.

The java.nio.file package provides a file change notification API, called the Watch Service API. It enables us to register a folder with the watch service. When registering, we tell the service which types of events we are interested in: file creation, file modification, or file deletion.

You may also like: Java IO and NIO

When the service detects an event of interest, it is forwarded to the registered process and handled as needed. This is basically how it works:

  1. The first step is to create a new WatchService by using the newWatchService() method of the FileSystem class.
  2. Next, we register a Path instance for the folder to be monitored with the types of events that we are interested in.
  3. And at last, we implement an infinite loop to wait for incoming events. When an event occurs, the key is signaled and placed into the watcher's queue. After processing its events, we need to put it back into a ready state by invoking its reset() method. If it returns false, the key is no longer valid and the loop can exit. 
Java




x
11


 
1
WatchService watchService = FileSystems.getDefault().newWatchService();
2
Path path = Paths.get("c:\\directory");
3
path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
4
boolean poll = true;
5
while (poll) {
6
  WatchKey key = watchService.take();
7
  for (WatchEvent<?> event : key.pollEvents()) {
8
    System.out.println("Event kind : " + event.kind() + " - File : " + event.context());
9
  }
10
  poll = key.reset();
11
}



This is the console output:

Java




xxxxxxxxxx
1


1
Event kind : ENTRY_CREATE - File : file.txt
2
Event kind : ENTRY_DELETE - File : file.txt
3
Event kind : ENTRY_CREATE - File : test.txt
4
Event kind : ENTRY_MODIFY - File : test.txt



The WatchService API is fairly low level, allowing us to customize it. In this article, and following the Observer pattern, we are going to design a high-level API on top of this mechanism for listening to file events for a given folder. We will begin by creating a FileEvent class, which extends the java.util.EventObject from which all event state objects shall be derived. A FileEvent instance is constructed with a reference to the source, which is logically the file upon which the event occurred upon. 

FileEvent.java

Java




xxxxxxxxxx
1
14


1
import java.io.File;
2
import java.util.EventObject;
3
 
          
4
public class FileEvent extends EventObject {
5
 
          
6
  public FileEvent(File file) {
7
    super(file);
8
  }
9
 
          
10
  public File getFile() {
11
    return (File) getSource();
12
  }
13
 
          
14
}



Next, we create the FileListener interface that must be implemented by an observer in order to be notified for file events. It extends the java.util.EventListener interface, which is a tagging interface that all event listener interfaces must extend.

FileListener.java

Java




xxxxxxxxxx
1
11


1
import java.util.EventListener;
2
 
          
3
public interface FileListener extends EventListener {
4
 
          
5
    public void onCreated(FileEvent event);
6
 
          
7
    public void onModified(FileEvent event);
8
 
          
9
    public void onDeleted(FileEvent event);
10
 
          
11
}



The last piece of the puzzle is to create the subject, which maintains the list of observers, and notifies them of any state changes, by calling one of their methods. We are going to name it FileWatcher and given a folder, this is how an instance of this class is constructed.

Java




xxxxxxxxxx
1
19


1
public class FileWatcher {
2
 
          
3
  protected List<FileListener> listeners = new ArrayList<>();
4
  protected final File folder;
5
 
          
6
  public FileWatcher(File folder) {
7
    this.folder = folder;
8
  }
9
 
          
10
  public List<FileListener> getListeners() {
11
    return listeners;
12
  }
13
 
          
14
  public FileWatcher setListeners(List<FileListener> listeners) {
15
    this.listeners = listeners;
16
    return this;
17
  }
18
 
          
19
}



It can implement the Runnable interface, so we can start the watch process with a daemon thread when invoking its watch() method if the folder exists.

Java




xxxxxxxxxx
1
16


 
1
public class FileWatcher implements Runnable {
2
 
          
3
  public void watch() {
4
    if (folder.exists()) {
5
      Thread thread = new Thread(this);
6
      thread.setDaemon(true);
7
      thread.start();
8
    }
9
  }
10
 
          
11
  @Override
12
  public void run() {
13
  // implementation not yet provided
14
  }
15
 
          
16
}



In the implementation of its run() method, a WatchService instance is created to poll for events within a try-with-resources statement. We will keep a track of it using a static final list in the FileWatcher class, so we can later invoke its close() method to cause any thread waiting to retrieve keys, to throw the unchecked ClosedWatchServiceException, which will interrupt the watch process in a clean way. Therefore, we will get no memory leak warnings when the application is being gracefully shutdown.

Java




xxxxxxxxxx
1


1
@Override 
2
public void contextDestroyed(ServletContextEvent event) {
3
  for (WatchService watchService : FileWatcher.getWatchServices()){
4
    try {
5
      watchService.close();
6
    } catch (IOException e) {
7
    }
8
  }
9
}



Java




xxxxxxxxxx
1
33


1
public class FileWatcher implements Runnable {
2
 
          
3
  protected static final List<WatchService> watchServices = new ArrayList<>();
4
 
          
5
  @Override
6
  public void run() {
7
    try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
8
      Path path = Paths.get(folder.getAbsolutePath());
9
      path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
10
      watchServices.add(watchService);
11
      boolean poll = true;
12
      while (poll) {
13
        poll = pollEvents(watchService);
14
      }
15
    } catch (IOException | InterruptedException | ClosedWatchServiceException e) {
16
       Thread.currentThread().interrupt();
17
    }
18
  }
19
 
          
20
  protected boolean pollEvents(WatchService watchService) throws InterruptedException {
21
    WatchKey key = watchService.take();
22
    Path path = (Path) key.watchable();
23
    for (WatchEvent<?> event : key.pollEvents()) {
24
      notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile());
25
    }
26
    return key.reset();
27
  }
28
 
          
29
  public static List<WatchService> getWatchServices() {
30
    return Collections.unmodifiableList(watchServices);
31
  }
32
 
          
33
}



Whenever an event occurs, the file path is resolved and the listeners are notified accordingly. If it is the creation of a new folder, another FileWatcher instance will be created for its monitoring. 

Java




xxxxxxxxxx
1
26


1
public class FileWatcher implements Runnable {
2
 
          
3
  protected void notifyListeners(WatchEvent.Kind<?> kind, File file) {
4
    FileEvent event = new FileEvent(file);
5
    if (kind == ENTRY_CREATE) {
6
      for (FileListener listener : listeners) {
7
         listener.onCreated(event);
8
      }
9
      if (file.isDirectory()) {
10
         // create a new FileWatcher instance to watch the new directory
11
         new FileWatcher(file).setListeners(listeners).watch();
12
      }
13
    } 
14
    else if (kind == ENTRY_MODIFY) {
15
      for (FileListener listener : listeners) {
16
        listener.onModified(event);
17
      }
18
    }
19
    else if (kind == ENTRY_DELETE) {
20
      for (FileListener listener : listeners) {
21
        listener.onDeleted(event);
22
      }
23
    }
24
  }
25
 
          
26
}



Here is the complete listing of the FileWatcher class.

FileWatcher.java

Java




xxxxxxxxxx
1
102


1
import static java.nio.file.StandardWatchEventKinds.*;
2
import java.io.File;
3
import java.io.IOException;
4
import java.nio.file.ClosedWatchServiceException;
5
import java.nio.file.FileSystems;
6
import java.nio.file.Path;
7
import java.nio.file.Paths;
8
import java.nio.file.WatchEvent;
9
import java.nio.file.WatchKey;
10
import java.nio.file.WatchService;
11
import java.util.ArrayList;
12
import java.util.Collections;
13
import java.util.List;
14
 
          
15
public class FileWatcher implements Runnable {
16
 
          
17
  protected List<FileListener> listeners = new ArrayList<>();
18
  protected final File folder;
19
  protected static final List<WatchService> watchServices = new ArrayList<>();
20
  
21
  public FileWatcher(File folder) {
22
    this.folder = folder;
23
  }
24
 
          
25
  public void watch() {
26
    if (folder.exists()) {
27
      Thread thread = new Thread(this);
28
      thread.setDaemon(true);
29
      thread.start();
30
    }
31
  }
32
 
          
33
  @Override
34
  public void run() {
35
    try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
36
      Path path = Paths.get(folder.getAbsolutePath());
37
      path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
38
      watchServices.add(watchService);
39
      boolean poll = true;
40
      while (poll) {
41
        poll = pollEvents(watchService);
42
      }
43
    } catch (IOException | InterruptedException | ClosedWatchServiceException e) {
44
       Thread.currentThread().interrupt();
45
    }
46
  }
47
 
          
48
  protected boolean pollEvents(WatchService watchService) throws InterruptedException {
49
    WatchKey key = watchService.take();
50
    Path path = (Path) key.watchable();
51
    for (WatchEvent<?> event : key.pollEvents()) {
52
      notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile());
53
    }
54
    return key.reset();
55
  }
56
 
          
57
  protected void notifyListeners(WatchEvent.Kind<?> kind, File file) {
58
    FileEvent event = new FileEvent(file);
59
    if (kind == ENTRY_CREATE) {
60
      for (FileListener listener : listeners) {
61
        listener.onCreated(event);
62
      }
63
      if (file.isDirectory()) {
64
        new FileWatcher(file).setListeners(listeners).watch();
65
      }
66
   } 
67
   else if (kind == ENTRY_MODIFY) {
68
     for (FileListener listener : listeners) {
69
       listener.onModified(event);
70
     }
71
   }
72
   else if (kind == ENTRY_DELETE) {
73
     for (FileListener listener : listeners) {
74
       listener.onDeleted(event);
75
     }
76
   }
77
  }
78
 
          
79
  public FileWatcher addListener(FileListener listener) {
80
    listeners.add(listener);
81
    return this;
82
  }
83
 
          
84
  public FileWatcher removeListener(FileListener listener) {
85
    listeners.remove(listener);
86
    return this;
87
  }
88
 
          
89
  public List<FileListener> getListeners() {
90
    return listeners;
91
  }
92
 
          
93
  public FileWatcher setListeners(List<FileListener> listeners) {
94
    this.listeners = listeners;
95
    return this;
96
  }
97
 
          
98
  public static List<WatchService> getWatchServices() {
99
    return Collections.unmodifiableList(watchServices);
100
  }
101
 
          
102
}



The final touch of our design can be the creation of a FileAdapter class, which provides a default implementation of the FileListener interface so that we can process only few of the events to save code.

FileAdapter.java

Java




xxxxxxxxxx
1
18


1
public abstract class FileAdapter implements FileListener {
2
 
          
3
  @Override
4
  public void onCreated(FileEvent event) {
5
    // no implementation provided
6
  }
7
 
          
8
  @Override
9
  public void onModified(FileEvent event) {
10
   // no implementation provided
11
  }
12
 
          
13
  @Override
14
  public void onDeleted(FileEvent event) {
15
   // no implementation provided
16
  }
17
 
          
18
}



The FileAdapter class is very useful in my case, to reload a Groovy script when developing a servlet application within my IDE. When a file is modified and republished in the deployment directory, it is first deleted before being recreated. Therefore, the modification event — which is fired twice on my Windows platform — can be ignored and its deletion counterpart is unusable in my context.

This is because, currently, we can't unregister a servlet, filter, or listener from the web container. Thus, I found no reason yet to have such feature enabled in production. Also, in this use case, performance is not even a concern since it will be hard to have even five packages to be watched by a different FileWatcher instance.

Java




xxxxxxxxxx
1
34


1
protected void loadScripts(File folder) {
2
   if (folder.exists()) {
3
    File[] files = folder.listFiles();
4
    if (files != null) {
5
      for (File file : files) {
6
        if (file.isFile()) {
7
        Object object = scriptManager.loadScript(file);
8
        register(object);
9
        } else {
10
        loadScripts(file);
11
        }
12
      }
13
        }
14
       watch(folder);
15
   }
16
}
17
 
          
18
protected void watch(File folder) {
19
  new FileWatcher(folder).addListener(new FileAdapter() {
20
    @Override
21
    public void onCreated(FileEvent event) {
22
      File file = event.getFile();
23
      if (file.isFile()) {
24
        logger.info("processing script " + file.getName());
25
        process(file);
26
     }
27
   }
28
  }).watch();
29
}
30
 
          
31
protected void process(File script) {
32
  Object object = scriptManager.loadScript(script);
33
  // update the application accordingly   
34
}



Even though it is said to use the Thread.sleep() method in a unit test is generally a bad idea, we are going to use it to write a test case for the FileWatcher class since we need a delay between the operations.

Java




xxxxxxxxxx
1
46


1
import static org.junit.Assert.*;
2
import java.io.File;
3
import java.io.FileWriter;
4
import java.io.IOException;
5
import java.util.HashMap;
6
import java.util.Map;
7
import org.junit.Test;
8
 
          
9
public class FileWatcherTest {
10
 
          
11
   @Test
12
   public void test() throws IOException, InterruptedException {
13
      File folder = new File("src/test/resources");
14
      final Map<String, String> map = new HashMap<>();
15
      FileWatcher watcher = new FileWatcher(folder);
16
      watcher.addListener(new FileAdapter() {
17
         public void onCreated(FileEvent event) {
18
           map.put("file.created", event.getFile().getName());
19
         }
20
         public void onModified(FileEvent event) {
21
           map.put("file.modified", event.getFile().getName());
22
         }
23
         public void onDeleted(FileEvent event) {
24
           map.put("file.deleted", event.getFile().getName());
25
         }
26
      }).watch();
27
      assertEquals(1, watcher.getListeners().size());
28
      wait(2000);
29
      File file = new File(folder + "/test.txt");
30
      try(FileWriter writer = new FileWriter(file)) {
31
        writer.write("Some String");
32
      }
33
      wait(2000);
34
      file.delete();
35
      wait(2000);
36
      assertEquals(file.getName(), map.get("file.created"));
37
      assertEquals(file.getName(), map.get("file.modified"));
38
      assertEquals(file.getName(), map.get("file.deleted"));
39
   }
40
 
          
41
   public void wait(int time) throws InterruptedException {
42
      Thread.sleep(time);
43
   }
44
 
          
45
}
46
 
          



In my previous article, "Groovify Your Java Servlets (Part 2): Scripting the JVM", I shown how to instantiate an object from a script with the Groovy Script Engine using a simple  ScriptManager class. This one may be the perfect opportunity for me to correct its implementation, by replacing the deprecated Class.newInstance() method with the Class.getConstructor().newInstance() method in order to make it right without the exceptions thrown.

Java
xxxxxxxxxx
1
23
 
1
import java.io.File;
2
import java.net.URL;
3
import groovy.util.GroovyScriptEngine;
4
 
          
5
public class ScriptManager {
6
 
          
7
    protected final GroovyScriptEngine engine;
8
 
          
9
    public ScriptManager(File folder) {
10
      engine = createScriptEngine(folder);
11
    }
12
 
          
13
    protected GroovyScriptEngine createScriptEngine(File folder) {
14
      URL[] urls = { folder.toURI().toURL() };
15
      return new GroovyScriptEngine(urls, this.getClass().getClassLoader());
16
    }
17
 
          
18
    public Object loadScript(String name) {
19
      return engine.loadScriptByName(name).getConstructor().newInstance()
20
    }
21
 
          
22
}
23
 
          


The class above can't load scripts located in the subdirectories of the given folder unless you pass the relative path in the script name argument. That is the reason why it is better to write it like this:

Java




xxxxxxxxxx
1
25


1
import java.io.File;
2
import java.net.URL;
3
import groovy.util.GroovyScriptEngine;
4
 
          
5
public class ScriptManager {
6
    
7
    protected final File folder;
8
    protected final GroovyScriptEngine engine;
9
 
          
10
    public ScriptManager(File folder) {
11
      this.folder = folder;
12
      engine = createScriptEngine();
13
    }
14
 
          
15
    protected GroovyScriptEngine createScriptEngine() {
16
      URL[] urls = { folder.toURI().toURL() };
17
      return new GroovyScriptEngine(urls, this.getClass().getClassLoader());
18
    }
19
 
          
20
    public Object loadScript(File file) {
21
      String name = file.getAbsolutePath().substring(folder.getAbsolutePath().length() + 1);
22
      return engine.loadScriptByName(name).getConstructor().newInstance()
23
    }
24
 
          
25
}



Further Reading

Java IO and NIO

Programming Models for Servers in Java

Topics:
java ,java nio ,monitor

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}