Filesystem watcher for monitoring directory changes with debouncing support.
- Directory Monitoring: Watch directories for file changes using Java NIO WatchService
- Event Debouncing: Prevent duplicate processing of rapid file modifications
- File Extension Filtering: Monitor only specific file types
- Thread-Safe: Concurrent listener management with CopyOnWriteArrayList
- Lifecycle Management: Start, stop, and close operations
- AutoCloseable: Try-with-resources support
- Event Types: CREATE, MODIFY, DELETE detection
- Scan Existing Files: Optionally process files present at startup
<dependency>
<groupId>org.flossware</groupId>
<artifactId>fs-watcher-java</artifactId>
<version>1.0</version>
</dependency>implementation 'org.flossware:fs-watcher-java:1.0'import org.flossware.fswatcher.*;
import java.nio.file.*;
// Configure watcher
WatcherConfig config = WatcherConfig.builder()
.watchDirectory(Paths.get("/var/app/configs"))
.addFileExtension("yaml")
.addFileExtension("json")
.debounceMillis(500)
.build();
// Create watcher
FileSystemDeploymentWatcher watcher = new FileSystemDeploymentWatcher(config);
// Add listener
watcher.addListener(new DeploymentEventListener() {
@Override
public void onDescriptorDetected(Path file) {
System.out.println("New file: " + file);
}
@Override
public void onDescriptorModified(Path file) {
System.out.println("Modified: " + file);
}
@Override
public void onDescriptorRemoved(Path file) {
System.out.println("Removed: " + file);
}
@Override
public void onError(Path file, Exception error) {
System.err.println("Error with " + file + ": " + error.getMessage());
}
});
// Start watching
watcher.start();
// ... application runs ...
// Stop when done
watcher.stop();WatcherConfig config = WatcherConfig.builder()
.watchDirectory(Paths.get("/var/app"))
.addFileExtension("txt")
.build();
try (FileSystemDeploymentWatcher watcher = new FileSystemDeploymentWatcher(config)) {
watcher.addListener(new MyEventListener());
watcher.start();
// Keep application running
Thread.sleep(60000);
// Watcher automatically stops when exiting try block
}WatcherConfig config = WatcherConfig.builder()
.watchDirectory(Paths.get("/path/to/watch")) // Required
.autoStart(true) // Default: true
.autoDeploy(true) // Default: true
.addFileExtension("yaml") // Can call multiple times
.addFileExtension("json")
.debounceMillis(500) // Default: 500ms
.build();Configuration Options:
watchDirectory(Path)- Directory to monitor (required)autoStart(boolean)- Whether to start automatically (default: true)autoDeploy(boolean)- Whether to auto-deploy detected files (default: true)addFileExtension(String)- Add file extension to monitor (default: yaml, json)fileExtensions(Set<String>)- Replace all file extensionsdebounceMillis(long)- Debounce delay in milliseconds (default: 500)
public interface DeploymentEventListener {
void onDescriptorDetected(Path descriptorFile);
void onDescriptorModified(Path descriptorFile);
void onDescriptorRemoved(Path descriptorFile);
void onError(Path file, Exception error);
}watcher.addListener(new DeploymentEventListener() {
@Override
public void onDescriptorDetected(Path file) {
// Handle new file
}
@Override
public void onDescriptorModified(Path file) {
// Handle modified file
}
@Override
public void onDescriptorRemoved(Path file) {
// Handle removed file
}
@Override
public void onError(Path file, Exception error) {
// Handle errors
}
});File changes are debounced to prevent duplicate processing when files are modified multiple times in quick succession (e.g., during text editor saves).
// Without debouncing: 5 modify events
// With 500ms debouncing: 1 modify event
WatcherConfig config = WatcherConfig.builder()
.watchDirectory(path)
.debounceMillis(500) // Wait 500ms for additional changes
.build();How It Works:
- File modification detected
- Timer started for debounce delay
- Another modification detected → timer reset
- No modifications for debounce period → event fired
- Only one event delivered for multiple rapid changes
- Configuration Hot Reload: Monitor config files and reload on changes
- File Processing: Watch upload directories for new files
- Development Tools: Monitor source files for changes (auto-rebuild)
- Deployment: Watch for deployment descriptor updates
- Log Processing: Monitor log directories for new log files
public class ConfigWatcher {
private final FileSystemDeploymentWatcher watcher;
private volatile Properties config;
public ConfigWatcher(Path configDir) throws Exception {
WatcherConfig cfg = WatcherConfig.builder()
.watchDirectory(configDir)
.addFileExtension("properties")
.build();
this.watcher = new FileSystemDeploymentWatcher(cfg);
watcher.addListener(new DeploymentEventListener() {
@Override
public void onDescriptorDetected(Path file) {
reloadConfig(file);
}
@Override
public void onDescriptorModified(Path file) {
reloadConfig(file);
}
@Override
public void onDescriptorRemoved(Path file) {
// Config removed
}
@Override
public void onError(Path file, Exception error) {
System.err.println("Config error: " + error);
}
});
watcher.start();
}
private void reloadConfig(Path file) {
try {
Properties props = new Properties();
props.load(Files.newInputStream(file));
this.config = props;
System.out.println("Config reloaded from " + file);
} catch (Exception e) {
System.err.println("Failed to reload config: " + e);
}
}
}public class UploadProcessor {
public void startWatching(Path uploadDir) throws Exception {
WatcherConfig config = WatcherConfig.builder()
.watchDirectory(uploadDir)
.addFileExtension("csv")
.addFileExtension("txt")
.debounceMillis(1000) // Wait for complete uploads
.build();
FileSystemDeploymentWatcher watcher = new FileSystemDeploymentWatcher(config);
watcher.addListener(new DeploymentEventListener() {
@Override
public void onDescriptorDetected(Path file) {
processFile(file);
}
@Override
public void onDescriptorModified(Path file) {
// Ignore modifications
}
@Override
public void onDescriptorRemoved(Path file) {
// File removed before processing
}
@Override
public void onError(Path file, Exception error) {
// Log error
}
});
watcher.start();
}
private void processFile(Path file) {
// Process the uploaded file
System.out.println("Processing: " + file);
}
}All operations are thread-safe:
- Listener management uses
CopyOnWriteArrayList - Event processing uses concurrent data structures
- Multiple threads can add/remove listeners safely
- Java 21 or higher
- SLF4J API (for logging)
MIT License - see LICENSE file for details.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
See CHANGELOG.md for version history.