skip to content

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?

level: middleimportance: should knowfreq 40%

answer

  1. UP-TO-DATE = in-place outputs survive
  2. clean deletes build/ -> up-to-date useless
  3. FROM-CACHE = outputs restored by input hash
  4. cache survives clean / branches / machines
  5. two outcomes, two mechanisms

basics

~20 s

Up-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 s

Two 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
bash
./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-CACHE

go deeper

for a junior

Know that up-to-date skips re-runs only while outputs exist, while the cache restores outputs after clean.

for a middle

Contrast the two mechanisms, name the FROM-CACHE vs UP-TO-DATE outcomes, and explain cross-clean/branch benefit.

for a senior

Explain the input-hash cache key concept and why the cache is independent of the build directory, enabling cross-machine reuse.

for a principal

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.

context