skip to content

Cache Key And Hit Diagnosis

How the cache key is computed from task type, inputs, and classpath, and how caching debug output explains a miss. Asked because 'the cache never hits' is a diagnosis exercise, not a configuration one.

on this pageshow

questions

5

When you run a Gradle build, what does the FROM-CACHE outcome next to a task mean, and how does it differ from UP-TO-DATE and executed?

level: juniorimportance: must knowfreq 70%

answer

  1. FROM-CACHE = unpacked from cache
  2. UP-TO-DATE = local incremental, unchanged
  3. executed = ran from scratch
  4. key = hash of inputs/classpath/type
  5. needs caching enabled + @CacheableTask

basics

~10 s

FROM-CACHE means Gradle restored the task's outputs from the build cache instead of running it. UP-TO-DATE means outputs were already on disk and unchanged. Executed means the task actually ran.

solid answer

~40 s

Gradle prints an outcome label per task. **Executed** (no label, or shown in `--info`) means the task body ran. **UP-TO-DATE** means incremental build saw the inputs/outputs unchanged on this machine, so it skipped re-running. **FROM-CACHE** means up-to-date checks failed (outputs were missing or stale) but Gradle found a cache entry keyed by the task's inputs and *unpacked* it into the output directory instead of executing. **NO-SOURCE** means the task had no inputs to act on. The key insight: UP-TO-DATE is local incremental state; FROM-CACHE is cross-build/cross-machine reuse via the build cache. Both require the task to be cacheable and the cache enabled (`org.gradle.caching=true`). Seeing FROM-CACHE confirms a cache hit; seeing executed where you expected FROM-CACHE signals a cache *miss* worth diagnosing.

code

bash · 4 lines
bash
./gradlew clean build --build-cache --console=verbose
# > Task :compileJava FROM-CACHE
# > Task :processResources UP-TO-DATE
# > Task :test FROM-CACHE

go deeper

for a junior

Name the three outcomes and that FROM-CACHE means outputs were restored from the cache, not executed.

for a middle

Distinguish local incremental (UP-TO-DATE) from content-addressed cache reuse (FROM-CACHE) and list the preconditions.

for a senior

Explain how the cache key drives FROM-CACHE across machines and what it means when an expected hit becomes executed.

for a principal

Frame outcomes as the observable signal for cache effectiveness across a CI fleet and how outcome ratios feed build-performance governance.

## Task outcomes in Gradle Every task in a Gradle build finishes with an **outcome** that Gradle reports (visibly with `--console=verbose` or in `--info`): - **(no label) / Executed** — the task's actions ran from scratch. - **UP-TO-DATE** — Gradle's *incremental build* (a.k.a. up-to-date checking) determined the task's declared inputs and outputs are unchanged since the last run on this machine, so it skipped execution and kept the existing outputs. - **FROM-CACHE** — up-to-date checking failed (e.g. outputs deleted by `clean`, or you're on a fresh checkout/CI agent), but the **build cache** held an entry whose key matches this task's current inputs, so Gradle *unpacked* the stored outputs into place instead of running the task. - **NO-SOURCE** — the task is up-to-date trivially because it has inputs declared but none are present. - **SKIPPED** — excluded via `onlyIf`, `-x`, or unmet condition. ## Why FROM-CACHE and UP-TO-DATE differ UP-TO-DATE is purely *local* and *temporal*: it compares against the previous build's state on the same machine. The build cache is a *content-addressable store*: each cacheable task computes a **cache key** (a hash of its task type, all declared inputs, the runtime classpath, and the identity of its outputs), and the packed outputs are stored under that key. On a later build — even on a different machine or after `clean` — if the same key is recomputed, Gradle pulls the entry. That's what enables CI agents and teammates to reuse each other's work. ## Preconditions For FROM-CACHE you need: (1) the cache enabled (`org.gradle.caching=true` or `--build-cache`); (2) the task marked cacheable (`@CacheableTask` or `outputs.cacheIf { }`); and (3) a matching entry already in the local or remote cache. If any input changed, the key changes and you get a miss (the task executes and stores a *new* entry). ```bash # Force a cache miss by cleaning, then observe outcomes ./gradlew clean ./gradlew build --build-cache --console=verbose # tasks now print FROM-CACHE where keys matched a prior entry ``` ## Practical reading When a task you *expected* to be FROM-CACHE shows as executed, an input changed the key — that is the start of a cache-miss diagnosis (see `-Dorg.gradle.caching.debug=true`).

  • If a task shows UP-TO-DATE, will it ever publish a new cache entry?
    No. UP-TO-DATE means it didn't run, so there are no fresh outputs to pack; the existing entry (if any) stays. Only executed cacheable tasks store new entries.
  • Why might the very first CI build show executed everywhere even with caching on?
    An empty/cold cache has no entries to match, so every key is a miss; tasks execute and seed the cache for subsequent builds.

UP-TO-DATE is finding last night's leftovers still in your own fridge; FROM-CACHE is grabbing an identical pre-made meal from a shared pantry keyed by its recipe — works even on a kitchen you've never used.

saying these in an interview costs you the question

  • Claiming FROM-CACHE and UP-TO-DATE are the same thing
  • Thinking UP-TO-DATE tasks repopulate the cache
  • Assuming caching works without enabling org.gradle.caching

context

open as a page

A task you expect to be FROM-CACHE keeps executing. How do you use -Dorg.gradle.caching.debug=true to find the differing input?

level: middleimportance: must knowfreq 55%

basics

~10 s

Run the build twice with -Dorg.gradle.caching.debug=true. It prints each hashed key component and the final key per task. Diff the two outputs; the component that differs is the input that broke the cache.

open as a page

What goes into a Gradle task's build-cache key? Walk through how the key is computed.

level: middleimportance: must knowfreq 60%

basics

~20 s

The cache key is a hash combining the task's type (its implementation class and classpath), each declared input property and input file's content, and the names of the declared output properties. Same key means a cache hit.

open as a page

What makes a task relocatable, and how can wrong input normalization cause false cache hits or chronic misses across machines?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A relocatable task produces the same key regardless of the project's absolute path. Wrong path-sensitivity puts absolute paths in the key, causing misses on other machines; missing/undeclared inputs cause false hits where stale outputs are wrongly restored.

open as a page

What are overlapping outputs, and why can they silently disable caching for a task even when its key is computed correctly?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Overlapping outputs are when two tasks write into the same directory. Gradle can't tell which files belong to which task, so it disables caching for the affected task to avoid packing the wrong files — even though its key is fine.

open as a page