Filesystem watching · JVM library

Act on files the moment they've finished landing.

PathWatcher watches a directory for created, modified and removed files — and tells you when a file is completely written, so you never grab a half-finished upload. Native JDK events where the OS allows, automatic polling on macOS. One dependency, nothing to install.

Java 8+ Native & poll modes Stable & active Apache-2.0
The idea

"Created" is not the same as "finished".

The moment a file appears is the moment it's least safe to touch — it may still be uploading. The classic bug is an ingestion job that reads a file mid-write and chokes on half a row. PathWatcher solves exactly that.

THE PROBLEM

A CREATE event fires too early

Raw filesystem APIs tell you a file was created, not that it's done. Act on the create and you race the writer — truncated reads, partial parses, corrupted imports.

THE ANSWER

A COMPLETELY_CREATED event fires when it's done

A completion worker watches each new file settle — by timeout or by retry — and only then raises FILE_COMPLETELY_CREATED. Register a handler and you receive files you can safely open, every time.

Quickstart

Watch a folder in a handful of lines.

Point it at a directory, register the handlers you care about, start. It picks the right scanner mode for the OS on its own.

  1. 01

    Add the dependency

    Maven or Gradle, from the Cantara repository.

  2. 02

    Watch & register handlers

    Created, modified, removed — and completely-created for safe processing.

  3. 03

    Start

    Native WatchService where available, polling on macOS — chosen automatically.

pom.xml

<!-- repository: https://mvnrepo.cantara.no -->
<dependency>
  <groupId>no.cantara.pathwatcher</groupId>
  <artifactId>pathwatch</artifactId>
  <version>1.71</version>
</dependency>

Ingest.java

PathWatcher pw = PathWatcher.getInstance();

pw.watch(Path.of("/srv/inbox"));

// fires only once the file is fully written and closed
pw.registerFileCompletelyCreatedHandler(event -> {
    Path file = event.getFile();
    ingest(file);   // safe: no partial reads
});

pw.registerRemovedHandler(event ->
    log.info("gone: {}", event.getFile()));

pw.start();
How it works

Producer, consumer, and a completion worker.

A producer discovers filesystem changes and queues them; a consumer drains the queue, waits for new files to settle, and invokes your callbacks. The scanner mode is chosen for the platform.

WATCH

Native or poll

JDK WatchService for event-driven I/O; polling where native events aren't available (macOS).

PRODUCE

Discover changes

Created, modified and removed files are detected and queued on a worker thread.

SETTLE

Wait for completion

A completion worker confirms a new file has stopped growing — by timeout or by retries.

CONSUME

Invoke handlers

The consumer thread calls your registered callbacks with a FileWatchEvent.

Capabilities

Everything a drop-folder pipeline needs.

Lifecycle events, safe-to-process completion, and cross-platform behaviour — with sensible defaults.

EVENTS

Created · modified · removed

Register a handler per lifecycle event; each fires with a FileWatchEvent carrying the path and file attributes.

COMPLETION

Completely-created

The headline feature: know when a file is fully written and closed before you open it — no more partial reads.

CROSS-PLATFORM

Native & poll, auto

Uses native WatchService when the JDK/OS supports it, and falls back to polling on macOS automatically.

BACKFILL

Existing files at startup

setScanForExistingFilesAtStartup(true) replays files already in the directory as create events on start.

TUNABLE

Completion modes

Choose TIMEOUT or RETRIES and set the interval, to match how your writers behave.

SELF-CONTAINED

Just the JDK & a queue

Event-driven I/O on worker threads, one small dependency footprint, no native binaries or host agents.

File lifecycle

From discovered to safe-to-process.

Every watched file moves through a small set of states. The one you usually wait for is COMPLETED — surfaced as the completely-created event.

StateMeaningEvent you receive
DISCOVEREDA new path has appeared in the watched directory.FILE_CREATED
INCOMPLETEThe file is still being written — not yet safe to read.FILE_MODIFY
COMPLETEDWriting has settled; the file is fully written and closed.FILE_COMPLETELY_CREATED
REMOVEDThe path was deleted from the watched directory.FILE_REMOVED
Use cases

Where it earns its keep.

Ingestion

Drop-folder ETL

An upstream system drops CSVs or XML into a directory. Process each one exactly when it's finished — never mid-write.

Uploads

Large files, safely

A 2 GB upload takes seconds to appear and minutes to finish. Wait for completely-created and skip the truncated-read bug entirely.

Integration

Legacy handoff by folder

Many systems still integrate by writing to a shared directory. PathWatcher turns that folder into a clean event stream.

Startup

Catch up on what's waiting

Replay files already sitting in the directory at boot, so nothing that arrived while you were down gets missed.

Where it fits

WatchService with the sharp edges filed off.

The JDK gives you raw filesystem events. PathWatcher adds the parts you'd otherwise write yourself — completion detection, macOS polling, and a clean handler API — and stays a small library.

  PathWatcher java.nio WatchService commons-io monitor inotify / fswatch
Created / modified / removed events
"File completely written" signal
Works on macOS out of the box
Clean per-event handler API
Pure JVM, no external tools
Replay existing files at startup

Honest positioning: if all you need is a raw create/modify/delete event, plain WatchService or commons-io will do. PathWatcher's reason to exist is the completely-created signal plus consistent macOS behaviour — the boilerplate every drop-folder pipeline ends up writing. It watches a single directory by design; for many independent trees, run several watchers or reach for a heavier framework.

Known limits

What to keep in mind.

Stable and in active use — with a few deliberate boundaries worth knowing up front.

One watch directory. A single instance watches one watchDir. Run multiple watchers for multiple trees.

Callbacks aren't thread-safe. Handlers run on the consumer thread; guard any shared state you touch from them.

macOS uses polling. The JDK has no native watch events on macOS, so PathWatcher polls there — slightly higher latency by design.

Completion is heuristic. "Done writing" is inferred by settling (timeout/retries); tune the mode to how your writers actually behave.

Stable · actively developed

Turn a directory into a clean event stream.

Add the dependency, watch a folder, and start receiving files you can safely open. That's the whole idea.