Watching a Directory for Changes in Java

JavaFilejava.nio.file

Java Problem Overview


I want to watch a directory for file changes. And I used WatchService in java.nio. I can successfully listen for file created event. But I can't listen for file modify event. I checked official java tutorial, but still struggling.

Here is the source code.

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class MainWatch {

	public static void watchDirectoryPath(Path path) {
		// Sanity check - Check if path is a folder
		try {
			Boolean isFolder = (Boolean) Files.getAttribute(path,
					"basic:isDirectory", NOFOLLOW_LINKS);
			if (!isFolder) {
				throw new IllegalArgumentException("Path: " + path
						+ " is not a folder");
			}
		} catch (IOException ioe) {
			// Folder does not exists
			ioe.printStackTrace();
		}

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

		// We obtain the file system of the Path
		FileSystem fs = path.getFileSystem();

		// We create the new WatchService using the new try() block
		try (WatchService service = fs.newWatchService()) {

			// We register the path to the service
			// We watch for creation events
			path.register(service, ENTRY_CREATE);
			path.register(service, ENTRY_MODIFY);
			path.register(service, ENTRY_DELETE);

			// Start the infinite polling loop
			WatchKey key = null;
			while (true) {
				key = service.take();

				// Dequeueing events
				Kind<?> kind = null;
				for (WatchEvent<?> watchEvent : key.pollEvents()) {
					// Get the type of the event
					kind = watchEvent.kind();
					if (OVERFLOW == kind) {
						continue; // loop
					} else if (ENTRY_CREATE == kind) {
						// A new Path was created
						Path newPath = ((WatchEvent<Path>) watchEvent)
								.context();
						// Output
						System.out.println("New path created: " + newPath);
					} else if (ENTRY_MODIFY == kind) {
						// modified
						Path newPath = ((WatchEvent<Path>) watchEvent)
								.context();
						// Output
						System.out.println("New path modified: " + newPath);
					}
				}

				if (!key.reset()) {
					break; // loop
				}
			}

		} catch (IOException ioe) {
			ioe.printStackTrace();
		} catch (InterruptedException ie) {
			ie.printStackTrace();
		}

	}

	public static void main(String[] args) throws IOException,
			InterruptedException {
		// Folder we are going to watch
		// Path folder =
		// Paths.get(System.getProperty("C:\\Users\\Isuru\\Downloads"));
		File dir = new File("C:\\Users\\Isuru\\Downloads");
		watchDirectoryPath(dir.toPath());
	}
    }

Java Solutions


Solution 1 - Java

Actually you have incorrectly subscribed to events. Only last listener has been registered with ENTRY_DELETE events type.

To register for all kind of events at once you should use:

 path.register(service, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); 

Solution 2 - Java

Warning! Shameless self promotion!

I have created a wrapper around Java 1.7's WatchService that allows registering a directory and any number of glob patterns. This class will take care of the filtering and only emit events you are interested in.

DirectoryWatchService watchService = new SimpleDirectoryWatchService(); // May throw
watchService.register( // May throw
        new DirectoryWatchService.OnFileChangeListener() {
            @Override
            public void onFileCreate(String filePath) {
                // File created
            }
    
            @Override
            public void onFileModify(String filePath) {
                // File modified
            }
            
            @Override
            public void onFileDelete(String filePath) {
                // File deleted
            }
        },
        <directory>, // Directory to watch
        <file-glob-pattern-1>, // E.g. "*.log"
        <file-glob-pattern-2>, // E.g. "input-?.txt"
        ... // As many patterns as you like
);

watchService.start();

Complete code is in this repo.

Solution 3 - Java

I made some classes for this.

public interface FileAvailableListener {
    public void fileAvailable(File file) throws IOException;
}

and

public class FileChange {

private long lastModified;
private long size;
private long lastCheck;

public FileChange(File file) {
	this.lastModified=file.lastModified();
	this.size=file.length();
	this.lastCheck = System.currentTimeMillis();
}

public long getLastModified() {
	return lastModified;
}
public long getSize() {
	return size;
}
public long getLastCheck() {
	return lastCheck;
}

public boolean isStable(FileChange other,long stableTime) {
	boolean b1 = (getLastModified()==other.getLastModified());
	boolean b2 = (getSize()==other.getSize());
	boolean b3 = ((other.getLastCheck()-getLastCheck())>stableTime);
	return b1 && b2 && b3;
}
}

and

public class DirectoryWatcher {

private Timer timer;
private List<DirectoryMonitorTask> tasks = new ArrayList<DirectoryMonitorTask>();

public DirectoryWatcher() throws URISyntaxException, IOException, InterruptedException {
	super();
	timer = new Timer(true);		
}
public void addDirectoryMonitoringTask(DirectoryMonitorTask task,long period) {
	tasks.add(task);
	timer.scheduleAtFixedRate(task, 5000, period);		
}
public List<DirectoryMonitorTask> getTasks() {
	return Collections.unmodifiableList(tasks);
}
public Timer getTimer() {
	return timer;
}
}

and

class DirectoryMonitorTask extends TimerTask {

public final static String DIRECTORY_NAME_ARCHIVE="archive";
public final static String DIRECTORY_NAME_ERROR="error";
public final static String LOCK_FILE_EXTENSION=".lock";
public final static String ERROR_FILE_EXTENSION=".error";	
public final static String FILE_DATE_FORMAT="yyyyMMddHHmmssSSS";

private String name;
private FileAvailableListener listener;
private Path directory;
private File directoryArchive;
private File directoryError;
private long stableTime;
private FileFilter filter;
private WatchService watchService;
private SimpleDateFormat dateFormatter = new SimpleDateFormat(FILE_DATE_FORMAT);
private Hashtable<File,FileChange> fileMonitor = new Hashtable<File,FileChange>();

public DirectoryMonitorTask(String name,FileAvailableListener listener,Path directory,long stableTime,FileFilter filter) throws IOException {
	super();
	this.name=name;
	this.listener=listener;
	this.directory=directory;
	this.stableTime=stableTime;
	if (stableTime<1) {
		stableTime=1000;
	}
	this.filter=filter;
	validateNotNull("Name",name);
	validateNotNull("Listener",listener);
	validateNotNull("Directory",directory);
	validate(directory);
	directoryArchive = new File(directory.toFile(),DIRECTORY_NAME_ARCHIVE);
	directoryError = new File(directory.toFile(),DIRECTORY_NAME_ERROR);
	directoryArchive.mkdir();
	directoryError.mkdir();
	//
	log("Constructed for "+getDirectory().toFile().getAbsolutePath());
	
	initialize();
	//
	watchService = FileSystems.getDefault().newWatchService();
	directory.register(watchService,StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_DELETE,StandardWatchEventKinds.ENTRY_MODIFY);
	log("Started");
}

private void initialize() {
	File[] files = getDirectory().toFile().listFiles();
	for (File file : files) {
		if (isLockFile(file)) {
			file.delete();
		} else if (acceptFile(file)) {
			fileMonitor.put(file,new FileChange(file));
			log("Init file added -"+file.getName());
		}
	}
}
public SimpleDateFormat getDateFormatter() {
	return dateFormatter;
}
public Path getDirectory() {
	return directory;
}
public FileAvailableListener getListener() {
	return listener;
}
public String getName() {
	return name;
}
public WatchService getWatchService() {
	return watchService;
}
public long getStableTime() {
	return stableTime;
}
public File getDirectoryArchive() {
	return directoryArchive;
}
public File getDirectoryError() {
	return directoryError;
}
public FileFilter getFilter() {
	return filter;
}	
public Iterator<File> getMonitoredFiles() {
	return fileMonitor.keySet().iterator();
}

@Override
public void run() {
	WatchKey key;
	try {
		key = getWatchService().take();
		// Poll all the events queued for the key
		for (WatchEvent<?> event : key.pollEvents()) {										
			@SuppressWarnings("unchecked")
			Path filePath = ((WatchEvent<Path>) event).context();
			File file = filePath.toFile();
			if ((!isLockFile(file)) && (acceptFile(file))) {
				switch (event.kind().name()) {
					case "ENTRY_CREATE":
						//							
						fileMonitor.put(file,new FileChange(file));
						log("File created ["+file.getName()+"]");
						break;
						//
					case "ENTRY_MODIFY":
						//							
						fileMonitor.put(file,new FileChange(file));
						log("File modified ["+file.getName()+"]");
						break;	
						//
					case "ENTRY_DELETE":
						//
						log("File deleted ["+file.getName()+"]");
						createLockFile(file).delete();
						fileMonitor.remove(file);							
						break;
						//
				}
			}
		}
		// reset is invoked to put the key back to ready state
		key.reset();
	} catch (InterruptedException e) {				
		e.printStackTrace();
	}
	
	Iterator<File> it = fileMonitor.keySet().iterator();

	while (it.hasNext()) {
		File file = it.next();	
		FileChange fileChange = fileMonitor.get(file);
		FileChange fileChangeCurrent = new FileChange(file);
		
		if (fileChange.isStable(fileChangeCurrent, getStableTime())) {
			log("File is stable ["+file.getName()+"]");
			String filename = getDateFormatter().format(new Date())+"_"+file.getName();
			File lockFile = createLockFile(file);
			if (!lockFile.exists()) {
				log("File do not has lock file ["+file.getName()+"]");
				try {
					Files.createFile(lockFile.toPath());
					log("Processing file ["+file.getName()+"]");
					getListener().fileAvailable(file);						
					file.renameTo(new File(getDirectoryArchive(),filename));
					log("Moved to archive file ["+file.getName()+"]");
				} catch (IOException e) {						
					file.renameTo(new File(getDirectoryError(),filename));
					createErrorFile(file,e);
					log("Moved to error file ["+file.getName()+"]");
				} finally {
					lockFile.delete();
					
				}
			} else {					
				log("File do has lock file ["+file.getName()+"]");
				fileMonitor.remove(file);
			}				
		} else {				
			log("File is unstable ["+file.getName()+"]");
			fileMonitor.put(file,fileChangeCurrent);
		}
	}		
}

public boolean acceptFile(File file) {
	if (getFilter()!=null) {
		return getFilter().accept(file);
	} else {
		return true;
	}		
}

public boolean isLockFile(File file) {
	int pos = file.getName().lastIndexOf('.');
	String extension="";
	if (pos!=-1) {
		extension = file.getName().substring(pos).trim().toLowerCase();
	}	
	return(extension.equalsIgnoreCase(LOCK_FILE_EXTENSION));
}

private File createLockFile(File file) {
	return new File(file.getParentFile(),file.getName()+LOCK_FILE_EXTENSION);
}

private void createErrorFile(File file,IOException exception) {
	File errorFile = new File(file.getParentFile(),file.getName()+ERROR_FILE_EXTENSION);
	
	StringWriter sw = null;
	PrintWriter pw = null;
	FileWriter fileWriter = null;
	try {
		//			
		fileWriter = new FileWriter(errorFile);
		if (exception!=null) {
			sw = new StringWriter();
			pw = new PrintWriter(sw);
			exception.printStackTrace(pw);		
			fileWriter.write(sw.toString());
		} else {
			fileWriter.write("Exception is null.");
		}
		//		
		fileWriter.flush();
		//
	} catch (IOException e) {
	} finally {
		if (sw!=null) {
			try {
				sw.close();
			} catch (IOException e1) {				
			}
		}
		if (pw!=null) {
			pw.close();
		}
		if (fileWriter!=null) {
			try {
				fileWriter.close();
			} catch (IOException e) {					
			}
		}
	}
}

private void validateNotNull(String name,Object obj) {
	if (obj==null) {
		throw new NullPointerException(name+" is null.");
	}			
}		
private void validate(Path directory) throws IOException {			
	File file = directory.toFile();
	if (!file.exists()) {
		throw new IOException("Directory ["+file.getAbsolutePath()+"] do not exists.");
	} else if (!file.isDirectory()) {
		throw new IOException("Directory ["+file.getAbsolutePath()+"] is not a directory.");
	} else if (!file.canRead()) {				
		throw new IOException("Can not read from directory ["+file.getAbsolutePath()+"].");
	} else if (!file.canWrite()) {
		throw new IOException("Can not write to directory ["+file.getAbsolutePath()+"] .");
	}		
}

private void log(String msg) {
	//TODO
	System.out.println("Task ["+getName()+"] "+msg);
}
}

Solution 4 - Java

package p1;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;

public class WatchForFile {

    public void WatchMyFolder(String path )
    {
    	File dir = new File(path);
    	Path myDir= dir.toPath();
    	  try 
    	  {
              Boolean isFolder = (Boolean) Files.getAttribute(myDir,"basic:isDirectory", NOFOLLOW_LINKS);
              if (!isFolder)
              {
                  throw new IllegalArgumentException("Path: " + myDir + " is not a folder");
              }
          }
    	  catch (IOException ioe)
    	  {
              ioe.printStackTrace();
          }

          System.out.println("Watching path: " + myDir);

        try {
           WatchService watcher = myDir.getFileSystem().newWatchService();
           myDir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);

           WatchKey watckKey = watcher.take();

           List<WatchEvent<?>> events = watckKey.pollEvents();
          
           for (WatchEvent event : events) {
                if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                    System.out.println("Created: " + event.kind().toString());
                  
                }
                if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
                    System.out.println("Delete: " + event.context().toString());
                }
                if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                    System.out.println("Modify: " + event.context().toString());
                }
            }
           
        }
        catch (Exception e) 
        {
            System.out.println("Error: " + e.toString());
        }
    }
}

Solution 5 - Java

Check this Code...

https://github.com/omkar9999/FileWatcherHandler

This project allows watching files for different file events like create, modify & delete and then act on these events in a generic way.

How to Use?
Create a Path object representing the directory to monitor for file events.

Path path = Paths.get("/home/omkar/test");

Implement the FileHandler interface to perform an action detected by file event registered.

public class FileHandlerTest implements FileHandler {

	private static final Logger LOGGER = Logger.getLogger(FileHandlerTest.class.getName());
	
	/*
	 * This implemented method will delete the file
	 * 
	 * @see com.io.util.FileHandler#handle(java.io.File,
	 * java.nio.file.WatchEvent.Kind)
	 */
	public void handle(File file, Kind<?> fileEvent) {
		LOGGER.log(Level.INFO,"Handler is triggered for file {0}",file.getPath());
		if(fileEvent == StandardWatchEventKinds.ENTRY_CREATE) {
			try {
				boolean deleted = Files.deleteIfExists(Paths.get(file.getPath()));
				assertTrue(deleted);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

Create an instance of an Implemented FileHandler

FileHandlerTest fileHandlerTest = new FileHandlerTest();

Create an instance of a FileWatcher by passing path, an instance of an Implemented FileHandler, and types of file events that you want to monitor separated by commas.

FileWatcher fileWatcher = new FileWatcher(path, fileHandlerTest, StandardWatchEventKinds.ENTRY_CREATE);

Now Create and start a new Thread.

Thread watcherThread = new Thread(fileWatcher);
watcherThread.start();

This thread will start polling for your registered file events and will invoke your custom handle method once any of the registered events are detected.

Solution 6 - Java

public class FileWatcher implements Runnable {

private static final Logger LOGGER =Logger.getLogger(FileWatcher.class.getName());

private WatchService watcher;
private FileHandler fileHandler;
private List<Kind<?>> watchedEvents;
private Path directoryWatched;

/**
 * @param directory
 * @Path directory to watch files into
 * @param fileHandler
 * @FileHandler implemented instance to handle the file event
 * @param watchRecursive
 *            if directory is to be watched recursively
 * @param watchedEvents
 *            Set of file events watched
 * 
 * @throws IOException
 */
public FileWatcher(Path directory, FileHandler fileHandler, boolean watchRecursive,
		WatchEvent.Kind<?>... watchedEvents) throws IOException {
	super();
	this.watcher = FileSystems.getDefault().newWatchService();
	this.fileHandler = fileHandler;
	this.directoryWatched = directory;
	this.watchedEvents = Arrays.asList(watchedEvents);
	if (watchRecursive) {
		// register all subfolders
		Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
			@Override
			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
				LOGGER.log(Level.INFO, "Registering {0} ", dir);
				dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,
						StandardWatchEventKinds.ENTRY_MODIFY);
				return FileVisitResult.CONTINUE;
			}
		});
	} else {
		directory.register(watcher, watchedEvents);
	}
}

@SuppressWarnings({ "unchecked" })
public void run() {
	LOGGER.log(Level.INFO, "Starting FileWatcher for {0}", directoryWatched.toAbsolutePath());
	WatchKey key = null;
	while (true) {
		try {
			key = watcher.take();
			if (key != null) {
				for (WatchEvent<?> event : key.pollEvents()) {
					WatchEvent.Kind<?> kind = event.kind();

					WatchEvent<Path> ev = (WatchEvent<Path>) event;
					//directory in which file event is detected
					Path directory = (Path) key.watchable(); 
					Path fileName = ev.context();
					if (watchedEvents.contains(kind)) {
						LOGGER.log(Level.INFO, "Invoking handle on {0}", fileName.toAbsolutePath());
						fileHandler.handle(directory.resolve(fileName).toFile(), kind);
					}
				}
				key.reset();
			}
		} catch (InterruptedException ex) {
			LOGGER.log(Level.SEVERE, "Polling Thread was interrupted ", ex);
			Thread.currentThread().interrupt();
		}
	}
}

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIsuruView Question on Stackoverflow
Solution 1 - JavavvgView Answer on Stackoverflow
Solution 2 - JavaHindolView Answer on Stackoverflow
Solution 3 - JavaFrederikHView Answer on Stackoverflow
Solution 4 - JavaP RAJESHView Answer on Stackoverflow
Solution 5 - Javaomkar maratheView Answer on Stackoverflow
Solution 6 - Javauser6440666View Answer on Stackoverflow