skip to content

What is Gradle's File System Watching (VFS), and what problem does it solve between consecutive builds?

level: juniorimportance: must knowfreq 55%

answer

  1. in-memory metadata + content hashes
  2. OS pushes change events to daemon
  3. retained across builds in daemon memory
  4. org.gradle.vfs.watch / --watch-fs
  5. avoids full re-snapshot

basics

~10 s

Gradle keeps an in-memory Virtual File System of file metadata and listens to OS file-change events. Between builds it retains that data so it doesn't re-read every file, making up-to-date checks faster.

solid answer

~40 s

Gradle maintains a **Virtual File System (VFS)** — an in-memory snapshot of file metadata and content hashes it has already inspected. Normally each build must re-stat and re-hash inputs to decide if tasks are up-to-date, which is expensive on large projects. With **file system watching** enabled (`org.gradle.vfs.watch=true`, default since Gradle 7 on supported OSes), the daemon registers with the OS file-watching API and receives change notifications for project files. The VFS is **retained across builds in the daemon's memory**: unchanged files keep their cached hashes, and only files the OS reported as changed get re-snapshotted. This avoids a full filesystem walk on every invocation, so incremental/up-to-date checks start much faster. It requires the daemon (it lives in daemon memory) and supported watch backends on macOS, Linux, and Windows.

code

properties · 3 lines
properties
# gradle.properties
org.gradle.vfs.watch=true
org.gradle.vfs.verbose=true   # logs retained vs invalidated paths

go deeper

for a junior

Know it's an in-memory file-metadata cache retained between builds, enabled by org.gradle.vfs.watch, that speeds up up-to-date checks.

for a middle

Explain retention in the daemon, OS change events, and that only changed paths are re-snapshotted.

for a senior

Connect VFS to input/output hashing, the daemon lifecycle, and graceful fallback when watch registration fails.

for a principal

Frame it as a fleet-wide developer-productivity lever, reason about its interaction with daemon memory budgets and CI vs. local trade-offs.

## What the VFS is Gradle's **Virtual File System (VFS)** is an in-memory model of the parts of the filesystem Gradle cares about: for each file it has seen, it stores metadata (existence, type, last-modified, size) and, for content-sensitive inputs, a **content hash**. Task input/output snapshots and the build cache key all depend on these hashes. Computing them means **stat-ing** files (reading directory entries and metadata) and **hashing** file content — work that scales with the number of inputs. ## The problem between builds Without retention, each new build starts with a cold VFS. To decide whether a task is `UP-TO-DATE`, Gradle must re-walk and re-snapshot the task's declared inputs/outputs, re-reading directories and re-hashing files. On a large multi-module repo with tens of thousands of files this filesystem scan dominates the 'configuration → up-to-date check' phase even when nothing changed. ## How file system watching fixes it With watching enabled, the Gradle **daemon** registers the project directory with the operating system's native file-watch API. The OS then pushes **change events** (created/modified/deleted) to the daemon as they happen, even between builds. Because the daemon process stays alive, the VFS it built last time is **retained in memory**. On the next build Gradle: 1. Trusts retained VFS entries for files the OS did **not** report as changed — no re-stat, no re-hash. 2. **Invalidates** only the paths the OS flagged, re-snapshotting just those. The net effect is that up-to-date checks read far fewer files from disk. ## Enabling / disabling - Property: `org.gradle.vfs.watch=true` in `gradle.properties` (default `true` on supported OSes since Gradle 7.0). - Per-invocation flags: `--watch-fs` / `--no-watch-fs`. - Verbose insight: `org.gradle.vfs.verbose=true` prints how many paths were retained vs. invalidated. ```properties # gradle.properties org.gradle.vfs.watch=true org.gradle.vfs.verbose=true ``` ## Requirements & limits - **Requires the daemon** — the VFS lives in daemon memory; `--no-daemon` means a cold VFS every time. - Supported on macOS, Linux, and Windows via native watch backends (see the dedicated backend question). - If watch registration fails (e.g. too many watched files, an unsupported filesystem), Gradle falls back to a non-retained snapshot and logs a warning — correctness is preserved, only the speed-up is lost. VFS watching is purely a **performance/incremental-correctness** optimization; it never changes build outputs.

  • Does VFS watching change the build's outputs or its up-to-date decisions?
    No. It only changes how cheaply Gradle gathers the input/output state. The same hashes drive the same up-to-date and cache decisions; watching just avoids re-reading unchanged files.
  • Why does it require the Gradle daemon?
    The retained VFS lives in the daemon process's heap. With `--no-daemon` the JVM exits after each build, so there is nothing to retain and watching gives no cross-build benefit.

Like a librarian who memorizes which shelves changed since you last visited instead of re-counting every book on every shelf each time you walk in.

saying these in an interview costs you the question

  • Claiming VFS watching makes builds 'incremental' on its own — incremental builds come from declared inputs/outputs; VFS only speeds up gathering that state.
  • Saying it works without a daemon.

context