DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Understanding Java Signals
  • JQueue: A Library to Implement the Outbox Pattern
  • Cordova: Communicating Between JavaScript and Java
  • How To Get Closer to Consistency in Microservice Architecture

Trending

  • How to Submit a Post to DZone
  • DZone's Article Submission Guidelines
  • How Large Tech Companies Architect Resilient Systems for Millions of Users
  • Unlocking AI Coding Assistants Part 4: Generate Spring Boot Application
  1. DZone
  2. Coding
  3. Java
  4. Watching Files With Java NIO

Watching Files With Java NIO

Learn more about watching files with Java NIO.

By 
Mamadou Lamine Ba user avatar
Mamadou Lamine Ba
·
Jan. 16, 20 · Tutorial
Likes (11)
Comment
Save
Tweet
Share
57.2K Views

Join the DZone community and get the full member experience.

Join For Free

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.

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

Programming Models for Servers in Java

Java (programming language) Event

Opinions expressed by DZone contributors are their own.

Related

  • Understanding Java Signals
  • JQueue: A Library to Implement the Outbox Pattern
  • Cordova: Communicating Between JavaScript and Java
  • How To Get Closer to Consistency in Microservice Architecture

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!