Walk through what happens on a configuration cache 'store' (miss) versus a 'load' (hit) run, and how Gradle decides which one occurs.
answer
- compute key → look up entry
- miss = store: configure, serialize, execute
- hit = load: deserialize, skip configuration, execute
- 'Calculating task graph' vs 'Reusing configuration cache'
- one entry per task request, with eviction
basics
~20 sOn a miss, Gradle runs configuration, serializes the task graph to an entry keyed by the build inputs, then executes. On a hit, it finds an entry whose key matches the current inputs, deserializes the graph, and executes without re-running configuration.
solid answer
~50 sEach invocation, Gradle computes the configuration cache key from the build's inputs (requested tasks, tracked system/Gradle properties, env vars, and files read during configuration). It then checks for a stored entry under `.gradle/configuration-cache/` with a matching key. **Store path (miss):** no matching entry, so Gradle runs initialization and configuration, records the inputs it reads, serializes the resulting task graph and configured task state into a new entry, and proceeds to execution — you see *"Calculating task graph as no configuration cache is available"*. **Load path (hit):** a matching entry exists, so Gradle deserializes the graph and jumps to execution — you see *"Reusing configuration cache"* and the configuration phase is skipped entirely. If any tracked input's value differs from what the entry recorded, the key no longer matches and Gradle falls back to the store path, replacing the stale entry. Gradle can keep multiple entries (one per distinct task request) up to a retention limit.
code
bash · 8 lines$ ./gradlew :app:assemble # miss
> Calculating task graph as no configuration cache is available for tasks: :app:assemble
$ ./gradlew :app:assemble # hit
> Reusing configuration cache.
$ ./gradlew :app:assemble -Pflag=x # tracked input changed -> miss again
> Calculating task graph as no configuration cache is available for tasks: :app:assemblego deeper
Recognize the two console messages and that a hit skips configuration.
Describe store vs load and what triggers a fall-back to store (changed tracked input).
Reason about amortization, per-request entries/eviction, and why store is slower than a plain run.
Plan CI hit-rate strategy (cache warming/persistence, stable invocations) and weigh storage/eviction trade-offs across pipelines.
## The decision each run 1. **Compute the key.** Gradle derives a key from the build inputs that can affect configuration: the requested tasks/CLI, tracked Gradle/system properties, environment variables, and files read during configuration. (On the very first run it can't know all inputs yet — it learns them as it configures.) 2. **Look up an entry.** It searches `.gradle/configuration-cache/` for an entry whose recorded inputs all still match. ## Store path (cache miss) No matching entry (first run, changed inputs, or evicted). Gradle: - runs **initialization** and **configuration** normally, - **tracks** every input read (props, env, files) so they become part of the entry's key, - **serializes** the resolved task graph + configured task state to a new cache entry, - continues to **execution**. Console shows: `Calculating task graph as no configuration cache is available for tasks: <tasks>`. ## Load path (cache hit) A matching entry exists. Gradle: - **deserializes** the stored task graph, - **skips** initialization and configuration entirely (build scripts are not evaluated), - goes straight to **execution**. Console shows: `Reusing configuration cache.` ## Invalidation = fall back to store If a tracked input changed — e.g. you passed a different `-Dprop`, edited a build script, or `gradle.properties` changed — the recorded inputs no longer match, so the lookup misses and Gradle takes the store path again, writing a fresh entry. This is transparent; you just see the "Calculating task graph" line return. ## Multiple entries Because the requested task set is part of the key, Gradle stores a **separate entry per distinct invocation** (e.g. `build`, `test`, `:app:run`), so alternating between common commands can each hit. There's a retention/eviction policy so the cache directory doesn't grow unbounded. ``` # miss -> store $ ./gradlew test -Denv=dev > Calculating task graph as no configuration cache is available for tasks: test # hit -> load $ ./gradlew test -Denv=dev > Reusing configuration cache. # changed tracked input -> miss -> store again $ ./gradlew test -Denv=prod > Calculating task graph as no configuration cache is available for tasks: test ``` ## Why it matters for performance The store run is *slower* than a no-cache run (it pays serialization cost), so the win is amortized across many subsequent load runs. On CI, where a fresh agent often has no entry, you pair the configuration cache with a *shared* mechanism or accept that the first run stores; on local dev the hit rate is high.
- Is the very first build with the configuration cache faster or slower than without it?Slightly slower — the store run pays the cost of tracking inputs and serializing the graph. The benefit is realized on subsequent load (hit) runs, so it amortizes over repeated builds.
- Why might CI see fewer hits than local development?A fresh CI agent often starts with an empty `.gradle/configuration-cache/`, so the first run is always a store/miss. Persisting or warming the cache directory, or stable invocations, improves CI hit rate.
- What console message tells you a hit occurred?`Reusing configuration cache.` A miss instead prints `Calculating task graph as no configuration cache is available for tasks: ...`.
saying these in an interview costs you the question
- Claiming the first (store) run is faster — it pays serialization overhead.
- Saying a hit re-runs build scripts — it does not; configuration is skipped.
- Assuming a single global entry — there is one per distinct task request, with eviction.