Why Java’s WatchService Misses File Changes and How to Fix It

This article examines a JDK bug that drops millisecond precision in file timestamps, explains why Java’s WatchService falls back to polling on macOS, contrasts it with Linux’s inotify‑based implementation, and shares practical workarounds for reliable file change detection.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Why Java’s WatchService Misses File Changes and How to Fix It

From a Real‑World Failure

Our configuration service writes files via an agent, and a client program watches those files for changes. The initial implementation used a dedicated thread that periodically polls each file’s last‑modified timestamp (millisecond precision) to detect updates.

Two drawbacks existed: the poll interval caused delayed detection, and if two changes occurred within the same millisecond the second could be missed. In practice these issues were minor—until a JDK bug surfaced.

The JDK Bug

In certain JDK versions (see bug 8177809 ) the File.lastModified() method loses millisecond precision and returns whole‑second timestamps. A demo on macOS showed that with JDK 1.8.0_261 the timestamps retained milliseconds, while JDK 11.0.6 truncated them.

WatchService – Java’s Built‑in File Watcher

Java provides WatchService to monitor a directory for create, modify, delete, and overflow events. A simple demo registers /tmp/file_test and writes to a file every 5 ms. Expected three modify events were observed only after about 9.5 seconds, indicating a polling delay.

public static void watchDir(String dir) {
    Path path = Paths.get(dir);
    try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
        path.register(watchService,
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_MODIFY,
            StandardWatchEventKinds.ENTRY_DELETE,
            StandardWatchEventKinds.OVERFLOW);
        while (true) {
            WatchKey key = watchService.take();
            for (WatchEvent<?> watchEvent : key.pollEvents()) {
                if (watchEvent.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                    System.out.println("create..." + System.currentTimeMillis());
                } else if (watchEvent.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                    System.out.println("modify..." + System.currentTimeMillis());
                } else if (watchEvent.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
                    System.out.println("delete..." + System.currentTimeMillis());
                } else if (watchEvent.kind() == StandardWatchEventKinds.OVERFLOW) {
                    System.out.println("overflow..." + System.currentTimeMillis());
                }
            }
            if (!key.reset()) {
                System.out.println("reset false");
                return;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

WatchService Internals

Debugging revealed that on macOS the returned instance is sun.nio.fs.PollingWatchService, which internally starts a thread that polls every 10 seconds. The polling logic mirrors our original implementation—reading the file’s last‑modified time and generating events based on changes—so it inherits the same JDK bug.

Inotify – Linux’s Native File Watcher

Linux provides the inotify system call for real‑time file change notifications. Java’s LinuxWatchService leverages this, offering immediate events without the 10‑second delay. A native library (e.g., Jinotify ) can expose inotify directly to Java, but it requires compiling a .so file.

Why macOS Falls Back to Polling

The JDK documentation notes that when the underlying platform does not support inotify, it falls back to a polling implementation. Consequently, on macOS the WatchService uses PollingWatchService, while on Linux it uses LinuxWatchService.

Verification with strace

Running strace -f -o s.txt java FileTime on Linux shows inotify system calls, confirming the native implementation.

How We Fixed the Issue

To avoid the timestamp bug, we added a separate version file containing the MD5 hash of the configuration file. The client reads the version file first; when the hash changes, it reloads the actual configuration.

We considered using WatchService directly, but in Docker environments inotify can lose events, a problem not limited to Java.

Takeaway

Even well‑tested APIs can hide platform‑specific bugs. Understanding the underlying implementation—polling versus inotify—helps choose the right strategy for reliable file change detection.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javabackend-developmentinotifyFile MonitoringWatchService
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.