skip to content

WatchService

WatchService registers a directory and delivers create, modify and delete events through WatchKeys you must reset after processing. It is the JDK's answer to polling a directory in a loop.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

Walk through the full WatchService usage lifecycle, from creating the service to processing events. Why is calling reset() essential?

level: middleimportance: must knowfreq 55%

answer

  1. create → register → take → drain → reset
  2. Key states: ready / signalled / invalid
  3. take() blocks, poll() doesn't, poll(timeout) bounded
  4. reset() re-arms; false means key invalid
  5. pollEvents() drains; forgetting reset() = silence

basics

~20 s

You create a WatchService, register a directory for the event kinds you want, then loop: block on take() until a key fires, drain its events with pollEvents(), handle each one, and call key.reset() to keep watching. Without reset() the key stops delivering new events.

solid answer

~50 s

The lifecycle is: (1) obtain the service via FileSystems.getDefault().newWatchService(); (2) register a directory Path with the event kinds (ENTRY_CREATE/MODIFY/DELETE), which returns a WatchKey; (3) enter a loop where you call ws.take() to block until a key is signalled (or poll()/poll(timeout) for non-blocking/bounded waits); (4) drain pending changes with key.pollEvents(), inspecting each WatchEvent's kind() and context(); (5) call key.reset() to re-arm the key. reset() is essential because after a key becomes signalled it stays in the signalled state and delivers no further events until reset returns it to the ready state; forgetting it is the classic bug where you get exactly one batch of events and then silence. reset() also returns false when the key is no longer valid (directory deleted or the service closed), which is your signal to stop processing that key. You should also handle OVERFLOW and close the service to release native resources.

code

java · 21 lines
java
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;

public class Watcher {
  public static void main(String[] args) throws Exception {
    Path dir = Paths.get("/var/inbox");
    try (WatchService ws = FileSystems.getDefault().newWatchService()) {
      dir.register(ws, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
      for (;;) {
        WatchKey key = ws.take();            // block until signalled
        for (WatchEvent<?> e : key.pollEvents()) {
          if (e.kind() == OVERFLOW) continue; // events were lost; re-scan if needed
          Path name = (Path) e.context();      // relative to dir
          Path full = dir.resolve(name);
          System.out.println(e.kind() + ": " + full);
        }
        if (!key.reset()) break;             // re-arm, or exit if invalid
      }
    }
  }
}

go deeper

for a junior

Can recite the basic loop order: register, wait, handle events, reset, and knows reset keeps it going.

for a middle

Explains the key state machine (ready/signalled/invalid), why reset() is mandatory, the take vs poll vs poll(timeout) distinction, and that context() is relative.

for a senior

Discusses resource management (close/cancel, ClosedWatchServiceException), draining semantics of pollEvents, and structuring the loop on a dedicated thread with interruption handling.

for a principal

Frames reset() as an ack-style backpressure mechanism, reasons about thread ownership and lifecycle in a service, and designs robust shutdown and error-recovery around invalid keys and OVERFLOW.

## The lifecycle in detail WatchService follows a deliberate **state machine** on its keys, and getting the loop right is the heart of using it correctly. ### Step 1 — Create the service ```java WatchService ws = FileSystems.getDefault().newWatchService(); ``` A `WatchService` is a closeable resource backed by native OS handles, so it belongs in a try-with-resources or must be explicitly `close()`d. ### Step 2 — Register a directory ```java Path dir = Paths.get("/var/inbox"); WatchKey key = dir.register(ws, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); ``` `register` is a method on **Path**, taking the service and a varargs of **event kinds**. It returns a **WatchKey** representing this registration. Re-registering the same directory updates the existing key's event set rather than creating a duplicate. ### The WatchKey state machine A WatchKey is always in one of three states: - **Ready** — registered and waiting; events accumulate but the key is not yet queued to the service. - **Signalled** — at least one event is pending; the key has been queued so `take()`/`poll()` can return it. Crucially, **once signalled it stays signalled** and will not be re-queued for new events until you reset it. - **Invalid** — no longer watching (directory deleted, key cancelled, or service closed). ### Step 3 — Retrieve a signalled key Three retrieval methods on the service: - `take()` — **blocks** until a key is signalled (like a blocking queue's take). Throws `InterruptedException`. - `poll()` — returns a signalled key immediately, or `null` if none. - `poll(long timeout, TimeUnit unit)` — waits up to the timeout, then returns `null`. ### Step 4 — Drain the events ```java for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); if (kind == StandardWatchEventKinds.OVERFLOW) { /* re-scan, continue */ } Path name = (Path) event.context(); // file name relative to dir Path full = dir.resolve(name); // absolute/resolved path } ``` `pollEvents()` **retrieves and removes** the pending events for that key (it drains the queue). Each `WatchEvent` carries a `kind()`, a `count()` (repeat count for coalesced events), and a `context()` (the entry name as a Path **relative to the watched directory** — resolve it against the dir to get the real path). ### Step 5 — reset() (the critical step) ```java boolean valid = key.reset(); if (!valid) { // directory inaccessible / key no longer valid → stop watching it } ``` `reset()` transitions the key from **signalled** back to **ready**, so it can be queued again for future events. **This is the single most common mistake:** if you do not call reset(), the key stays signalled, never gets re-queued, and your loop blocks forever on the next `take()` even though files keep changing. reset() returns `false` if the key is no longer valid — that is your cue to remove it from any map of watched directories and stop processing it. ### The canonical loop ```java for (;;) { WatchKey key = ws.take(); // 3: block for (WatchEvent<?> e : key.pollEvents()) { /* 4: handle */ } if (!key.reset()) break; // 5: re-arm or exit } ``` ### Cleanup When done, `ws.close()` releases the native resources and invalidates all keys; an active `take()` will then throw `ClosedWatchServiceException`. You can also stop watching one directory with `key.cancel()` without closing the whole service. ## Why reset() exists at all The signalled-until-reset design lets you safely drain a key's events on one thread while the service keeps accumulating new events behind it; resetting is your explicit acknowledgement that you have consumed the batch and are ready for the next. It mirrors the ack pattern of message queues.

  • What happens if you never call reset() on a signalled key?
    The key stays in the signalled state and is never re-queued, so the service delivers no further events for that directory and your take() loop blocks forever even though files keep changing.
  • How do you turn a WatchEvent's context into a usable absolute path?
    The context() is the entry name relative to the watched directory, so you resolve it against that directory: watchedDir.resolve((Path) event.context()).
  • What's the difference between key.cancel() and ws.close()?
    cancel() stops watching just that one directory and invalidates that single key; close() shuts down the entire service, invalidating all keys and causing a blocked take() to throw ClosedWatchServiceException.

reset() is like flipping a pager back to 'ready' after you've read the message — until you do, it won't beep again even if new pages pile up.

saying these in an interview costs you the question

  • Forgetting key.reset() and then claiming WatchService 'only fires once' as if that's a bug in the API
  • Treating context() as an absolute path instead of a name relative to the watched directory
  • Calling pollEvents() repeatedly expecting the same events (it drains them)
  • Not closing the service / leaking native handles
  • Assuming take() returns events directly rather than a WatchKey

context

open as a page

What is the Java WatchService API and what problem does it solve?

level: juniorimportance: should knowfreq 45%

basics

~20 s

WatchService is a built-in Java tool (in java.nio.file) that tells your program when files or folders change. Instead of repeatedly scanning a directory yourself, you register it and get notified when files are created, modified, or deleted.

open as a page

What is the OVERFLOW event in WatchService, when does it occur, and how should you handle it?

level: seniorimportance: should knowfreq 35%

basics

~20 s

OVERFLOW means the watch service dropped some change events because they arrived faster than your program consumed them. When you see it, you can't trust your event list is complete, so you should re-scan the directory to find out the real current state.

open as a page

WatchService only watches a single directory level. How do you watch an entire directory tree, and what edge cases must you handle?

level: seniorimportance: should knowfreq 40%

basics

~20 s

WatchService doesn't watch subfolders automatically. To watch a whole tree, you walk the directory tree once and register every directory, keep a map from each WatchKey to its directory, and whenever a new subdirectory is created (an ENTRY_CREATE you observe) you register that one too.

open as a page

In a high-throughput ingestion service, would you build it around WatchService or a different mechanism? Discuss the trade-offs and how you'd make WatchService production-grade.

level: principalimportance: nice to knowfreq 20%

basics

~20 s

WatchService is great for low-to-moderate change rates because it's efficient and event-driven. For very high throughput or strict reliability, you usually pair it with periodic reconciliation (or replace it), because it can drop events (OVERFLOW), behaves differently per OS, and isn't recursive. The robust design is event-driven for speed plus a safety net that re-scans.

open as a page