Enabling the build cache and Gradle's incremental/up-to-date checking are sometimes confused. After enabling caching, why might a 'clean' build still be fast, and how is that different from up-to-date checks?
answer
- UP-TO-DATE = in-place outputs survive
- clean deletes build/ -> up-to-date useless
- FROM-CACHE = outputs restored by input hash
- cache survives clean / branches / machines
- two outcomes, two mechanisms
basics
~20 sUp-to-date checks skip a task only if its outputs already exist in the build directory, so clean wipes them. The build cache, once enabled, restores outputs from a separate store keyed by inputs, so even after clean a matching task is unpacked FROM-CACHE instead of re-run.
solid answer
~40 sTwo different mechanisms. **Incremental / up-to-date checking** is always on and compares a task's current inputs/outputs to its previous run *in place*; if nothing changed and outputs still exist, the task is `UP-TO-DATE` and skipped. Running `clean` deletes those outputs, so the next build re-executes everything. The **build cache**, which you must explicitly enable, stores task outputs in a separate location keyed by a hash of the task's inputs. After `clean`, the outputs are gone from the build directory, but if the input hash matches a stored entry Gradle unpacks it and the task reports `FROM-CACHE` rather than re-running. That is why a clean build can still be fast once caching is enabled — and also why caching survives across branches and machines, which up-to-date checks cannot.
code
bash · 3 lines./gradlew test # first run: EXECUTED
./gradlew test # UP-TO-DATE (outputs still in build/)
./gradlew clean test --build-cache # not up-to-date after clean, but FROM-CACHEgo deeper
Know that up-to-date skips re-runs only while outputs exist, while the cache restores outputs after clean.
Contrast the two mechanisms, name the FROM-CACHE vs UP-TO-DATE outcomes, and explain cross-clean/branch benefit.
Explain the input-hash cache key concept and why the cache is independent of the build directory, enabling cross-machine reuse.
Relate to build-time strategy: incremental for local edit loops, build cache for clean/CI/cross-machine reuse, both layered.
## Two mechanisms, easily conflated When you enable the build cache you add a *second*, complementary acceleration mechanism. Understanding the boundary between them is a frequent interview discriminator. ### Up-to-date / incremental checking (always on) For each task, Gradle records a snapshot of its **inputs** (files, properties) and **outputs**. On the next run it re-snapshots. If inputs are unchanged *and* the declared outputs still exist in the build directory, the task is skipped with outcome `UP-TO-DATE`. This is purely *in place* — it relies on the outputs physically remaining where the task left them. Consequence: `./gradlew clean` deletes `build/`, so every output is gone and the next build re-runs everything. Up-to-date checking gives you nothing across a clean, across a fresh checkout, or across machines. ### The build cache (must be enabled) Once `org.gradle.caching=true` (or `--build-cache`) is set, Gradle additionally computes a **cache key** — a hash over the task's inputs, classpath, task implementation, and relevant environment. Cacheable task outputs are stored, packed, under that key. On a later build, if the recomputed key matches a stored entry, Gradle **unpacks the outputs from the cache** and reports the task as `FROM-CACHE`, skipping execution entirely. Because the store is independent of the `build/` directory: - A build after `clean` can be fast: outputs are restored from the cache, not rebuilt. - The cache works **across branches** (switch branches, switch back — outputs reappear) and, with a shared backend, **across machines**. ### Side-by-side | | Up-to-date check | Build cache | |---|---|---| | On by default? | Yes | No (must enable) | | Survives `clean`? | No | Yes | | Cross-machine? | No | Yes (with shared backend) | | Outcome label | `UP-TO-DATE` | `FROM-CACHE` | ### Why this answers the question A freshly enabled cache is what makes a `clean` build fast: the up-to-date mechanism can never help after `clean`, but the cache restores prior outputs by input-hash. Reading `--info` output, you will see `FROM-CACHE` (cache) versus `UP-TO-DATE` (incremental) — different outcomes from different machinery. ```bash ./gradlew clean test --build-cache --info # look for ':test' reported as FROM-CACHE on a repeat clean run ```
- After ./gradlew clean, why can up-to-date checking never skip a task?clean deletes the outputs in the build directory, so the up-to-date check sees missing outputs and forces re-execution. Only the build cache, which stores outputs separately, can restore them.
- What console outcomes distinguish the two mechanisms?UP-TO-DATE indicates the incremental check skipped the task in place; FROM-CACHE indicates the build cache unpacked stored outputs. EXECUTED means it actually ran.
Up-to-date checking is like leaving last night's leftovers on the counter — gone the moment you clear the counter. The build cache is a labeled fridge: clear the counter all you like; if the label (input hash) matches, you pull the meal back out ready-made.
saying these in an interview costs you the question
- Saying the build cache and up-to-date checks are the same thing.
- Claiming up-to-date checking survives a clean build.
- Believing enabling caching turns off incremental checks — both operate together.