Why Java’s WatchService Misses File Changes and How to Fix It
An in‑depth look at a real‑world file‑watching failure caused by a JDK timestamp bug, exploring Java’s WatchService implementation, its reliance on polling versus Linux’s inotify, and practical workarounds to ensure reliable configuration reloads across platforms.
Hello, I’m Xiao Lou. I recently investigated a file‑watching issue and documented the findings.
Starting from a failure
A configuration service writes files to clients via an agent, and the client monitors these files for changes. The initial implementation used a dedicated thread that periodically polls each file’s last‑modified timestamp (millisecond precision) to detect changes.
This approach has two drawbacks: it cannot detect changes in real time due to the polling interval, and if two changes occur within the same millisecond the later one may be missed.
In practice these issues seemed minor until a serious production incident occurred, caused by a JDK bug that truncates the file timestamp to whole seconds.
The bug is documented at JDK‑8177809 . On certain JDK versions (e.g., JDK 1.8.0_261) the last‑modified time loses millisecond precision, while newer versions (e.g., JDK 11.0.6) retain it. When two modifications happen within the same second, the second change is invisible, leading to the outage.
WatchService – Java’s built‑in file‑change monitor
Java provides WatchService to monitor a directory for create, modify, and delete events. A simple demo was written:
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();
}
}Running this on /tmp/file_test with writes every 5 ms should generate three modify events, but the first modify event only appears after about 9.5 seconds.
WatchService internals
Debugging shows that the actual implementation on macOS is PollingWatchService, which spawns a thread that polls file timestamps at a default interval (≈10 s). The polling logic mirrors the original manual approach, thus inheriting the same timestamp‑precision bug.
On Linux, the implementation switches to LinuxWatchService, which uses the kernel’s inotify system call for real‑time notifications.
inotify – Linux kernel file‑watch mechanism
Linux’s inotify provides immediate event delivery when a file changes. Java can leverage it via LinuxWatchService, but on macOS the class is absent, so the fallback polling implementation is used.
Experiments on both macOS and Linux confirmed that Linux receives many more events with near‑real‑time latency.
Verifying inotify usage
Using strace -f -o s.txt java FileTime shows that the Java process indeed invokes the inotify syscall on Linux.
How the failure was fixed
To work around the JDK timestamp bug, a version file was introduced. Each configuration file’s MD5 hash is written to a separate version file; the client watches the version file and reloads the configuration only when the version changes.
The team considered using inotify directly, but reports indicated that in Docker environments inotify events can be lost, a problem not unique to Java.
Conclusion
Some bugs only surface under specific conditions; thorough investigation and platform‑aware implementations are essential. When built‑in mechanisms are unreliable, pragmatic alternatives like version files can provide a robust solution.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xiao Lou's Tech Notes
Backend technology sharing, architecture design, performance optimization, source code reading, troubleshooting, and pitfall practices
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.
