skip to content

Even with the local build cache enabled, when does a task still get re-executed rather than restored FROM-CACHE?

level: seniorimportance: should knowfreq 30%

answer

  1. hit = archive matching current key
  2. changed input → new key → miss
  3. eviction or first-build → miss
  4. survives clean (unlike up-to-date)
  5. FROM-CACHE vs EXECUTED

basics

~20 s

If the task's inputs changed (so its cache key differs) there's no matching entry to restore; or the entry was evicted; or the cache lacks an entry for that key. Then the task runs and writes a new entry. A clean removes outputs but the cache can still restore them.

solid answer

~50 s

The local cache only short-circuits a task when the directory holds an archive whose name equals the task's current **cache key** (the hash of all declared inputs). It re-executes when: - **The key is new** — any input changed (source content, an input property, the task's implementation classpath, even the Gradle/plugin version contributing to the key), so no archive matches. - **No entry exists yet** — first time this key is built on the machine. - **The entry was evicted** — it aged out past `removeUnusedEntriesAfterDays` and got cleaned up. - **The cache or task isn't eligible** — caching disabled, or the task isn't marked cacheable. Key insight that distinguishes local cache from up-to-date checking: after `gradle clean`, up-to-date checking has nothing in `build/` to reuse, but the local cache **can** still restore outputs by key — that's its main extra value. On a hit the task is `FROM-CACHE`; on a miss it's executed and writes a fresh archive.

code

bash · 4 lines
bash
./gradlew build      # EXECUTED, outputs written to cache
./gradlew clean      # build/ deleted
./gradlew build      # FROM-CACHE: local cache restores by key,
                     # even though up-to-date checking had nothing to reuse

go deeper

for a junior

Know a task is restored only if nothing relevant changed; otherwise it runs again.

for a middle

Explain key = input hash, hit vs miss, and that the cache survives clean unlike up-to-date checking.

for a senior

Enumerate all miss causes (key change, no entry, eviction, ineligibility) and articulate the up-to-date contrast precisely.

for a principal

Connect this to build-determinism strategy: stable keys + persistent cache tiers as the lever for fast, reproducible org-wide builds.

## The decision Gradle makes per task For each cacheable task with caching on, Gradle: 1. Computes the **cache key** = a hash over every declared input (input file contents, input properties, output property names, the task type's implementation + classpath, and relevant runtime/plugin identity). 2. Looks in the local cache directory for an archive named with that key. 3. **Hit** → unpacks the archive into the output locations, marks the task `FROM-CACHE`, no execution. 4. **Miss** → runs the task, then (if `push` is on) packs its outputs into a new archive named by the key. ## When you get a MISS (re-execution) - **Inputs changed** → different hash → different key → no matching archive. This is the normal, correct behavior. - **First build of that key** → nothing cached yet. - **Entry evicted** → it exceeded the retention window and cleanup deleted it. - **Not cacheable / caching off** → the task never participates. ## The crucial contrast with up-to-date checking Up-to-date (incremental) checking asks: *are the current outputs in `build/` still valid for the current inputs?* If you ran `clean`, the outputs are gone, so up-to-date checking forces re-execution. The **build cache** asks a different question: *do I have an archived copy of outputs for this exact key anywhere in the cache?* So after `clean`, or on a fresh checkout, or when switching to a branch you built before, the cache can restore outputs that up-to-date checking could not. That is precisely the extra value of the local cache. ``` gradle build # task EXECUTED, output cached gradle clean # build/ wiped gradle build # up-to-date can't help (build/ empty), # but local cache → FROM-CACHE (same key) ``` ## Why a task might 'never' hit If a task re-executes every time even unchanged, its key is unstable — usually a non-reproducible input (absolute paths, timestamps, environment-sensitive properties) leaking into inputs. (Diagnosing *why* keys differ is the cache-key-diagnosis topic; here the point is simply that a changed key means a miss.) ## Summary - Hit requires an archive matching the current key. - Misses come from changed inputs, no prior entry, eviction, or ineligibility. - The local cache's headline benefit over up-to-date checking: it survives `clean` and fresh checkouts.

  • Why is the local cache more powerful than up-to-date checking after a `clean`?
    Up-to-date checking needs the outputs still present in `build/`; `clean` removes them. The cache restores outputs from an archive keyed by inputs, independent of `build/`.
  • Name two reasons a task re-executes despite caching being on.
    An input changed so the cache key is new, or the matching entry was evicted/never existed for that key.
  • What does a FROM-CACHE outcome mean?
    Gradle found an archive matching the task's cache key and unpacked its outputs instead of running the task.

saying these in an interview costs you the question

  • Claiming `clean` invalidates the build cache — it only wipes outputs; cached archives remain.
  • Saying the cache re-runs a task whenever you run it again, regardless of inputs — only key changes/misses cause that.
  • Conflating up-to-date (build/-bound) with cache restore (key-bound).

context