WatchService reports changes to registered watchable objects through signaled keys. The consumer retrieves a key, reads its events, and resets it so later changes can be signaled.
Java WatchService: register, consume, and reset each key
Operational contract
A watched directory is a hint source, not a durable message queue. Register the directory for the event kinds needed by the application, process a key's current batch, then inspect reset's boolean result. A false result means the registration is no longer valid and requires a new ownership decision. The event context for a Path registration is relative to the watched directory. The code keeps resolution local to that directory and returns whether observation remains active; the owner closes the service on shutdown.
Failure case
A depot process watches a staging folder where 47 manifests may arrive over a shift. One create notification prompts an intake check, but a modify notification can arrive while the producer is still writing. The consumer records candidate paths and waits for the publication contract described in the next lesson. It does not parse each event as a complete manifest or promise that every short-lived file will be observed.
Java code
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Set;
public class StagingWatchConsumer {
public record Outcome(boolean rescan, boolean active) {}
public static WatchService register(Path staging) throws IOException {
WatchService service = FileSystems.getDefault().newWatchService();
try {
staging.register(service, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
return service;
} catch (IOException | RuntimeException failure) {
try { service.close(); }
catch (IOException closeFailure) { failure.addSuppressed(closeFailure); }
throw failure;
}
}
public static Outcome takeOne(WatchService service, Path staging, Set<Path> candidates)
throws InterruptedException {
WatchKey key = service.take();
boolean rescan = false;
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
rescan = true;
continue;
}
Object context = event.context();
if (context instanceof Path) {
Path relative = (Path) context;
if (!relative.isAbsolute()) candidates.add(staging.resolve(relative).normalize());
}
}
return new Outcome(rescan, key.reset());
}
}Performance and ownership cost
Processing E delivered events is O(E) plus set insertion. The set retains O(U) distinct candidate paths, where U is the number of unique names. If the producer outruns the consumer, event loss can occur; reset and overflow handling must be paired with a directory reconciliation path.
Common Mistakes
- Do not forget to reset the key after processing its batch.
- Do not resolve event context against an unrelated working directory.
- Do not equate a create event with a finished file.
Connected lessons
- Java Files.move: atomic publication is a filesystem contract
- Java file I/O: UTF-8, streaming reads, and path ownership
- Java cancellation: timed waits and cooperative interruption
- Java walkFileTree: visit failures without hiding incomplete scans
- Java WatchService OVERFLOW: reconcile against directory state
- Java DirectoryStream: close iteration and reject silent truncation
- Java symbolic links: inspect attributes without claiming race safety
- Java file, JDBC, and subprocess boundaries quiz
- Advanced Java
