File Change Monitoring in Java: WatchService, JDK Bug, and Inotify
This article examines Java file change monitoring, explains the limitations of a simple timestamp‑polling approach, details a JDK bug that loses millisecond precision, explores the built‑in WatchService and its fallback to polling, and demonstrates how Linux inotify provides a more reliable solution.
The author encountered a file‑change‑monitoring problem in a configuration‑distribution service and shares a detailed investigation and solution.
Initially the implementation used a dedicated thread that periodically read each file's last-modified timestamp, which suffered from delayed detection and, due to a JDK bug, could lose millisecond precision, causing missed updates.
The bug (JDK‑8177809) was demonstrated on JDK 1.8.0_261 and JDK 11.0.6, showing that on affected versions the file timestamp is rounded to whole seconds, increasing the chance of undetected changes within the same second.
Discovering Java's WatchService , the author wrote a demo to watch a directory, but observed that on macOS the first modify event appeared after about 9.5 seconds, indicating the service was using a polling implementation.
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();
}
}Debugging revealed that WatchService instantiated a PollingWatchService on macOS, which internally polls file timestamps much like the original approach, and thus inherits the same precision bug.
On Linux, however, the service uses LinuxWatchService , which leverages the kernel's inotify mechanism. The author examined native inotify‑based libraries (e.g., Jinotify) and confirmed that Linux receives events promptly.
class sun.nio.fs.PollingWatchService class sun.nio.fs.LinuxWatchServiceExperiments comparing macOS and Linux showed many more events and near‑real‑time delivery on Linux. Using strace -f -o s.txt java FileTime captured inotify system calls, confirming the native implementation.
To work around the JDK bug, the team added a separate version file containing the MD5 of the configuration file; the application reloads the config only when the version changes, avoiding reliance on timestamp precision.
In conclusion, the author stresses that hidden bugs can cause production failures, encourages deep technical investigation, and suggests consulting domain experts when needed.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.