What build inputs make up the configuration cache key, and what causes Gradle to discard the entry and recompute the task graph?
answer
- key = requested tasks + props + env + files read
- separate entry per task request
- tracked reads via providers.* APIs
- changed tracked input → recompute graph
- untracked System.getenv at config time is a problem
basics
~20 sThe key includes the set of requested tasks, plus the values of any system properties, environment variables, Gradle properties, and files read during configuration. Change any of these and the entry is invalidated, so Gradle recomputes the graph.
solid answer
~50 sThe configuration cache entry is keyed by everything that can affect the result of the configuration phase. The main components are: the **task request** (the exact tasks and CLI args you asked for), the active **build/Gradle properties**, and any **system properties, environment variables, and files** that the build *reads during configuration*. Gradle tracks these reads so that, on the next build, it can compare them. A different set of requested tasks produces a different key (a separate entry). If a tracked input's value changes — say a system property the build read, or the content of a file like `gradle.properties` or a script — the existing entry no longer matches and Gradle recomputes and re-stores the graph. This is why reading env vars or files at configuration time should go through Gradle's tracked providers (`providers.environmentVariable`, `providers.systemProperty`, `providers.fileContents`), so the dependency is recorded as a cache input rather than silently captured.
code
kotlin · 12 lines// Make configuration-time reads tracked cache inputs
val gitSha = providers.environmentVariable("GIT_SHA")
val verFile = providers.fileContents(
layout.projectDirectory.file("version.txt")
).asText
tasks.register("stamp") {
// capture provider values, not raw System.getenv()
val sha = gitSha.orElse("local")
val ver = verFile.orElse("0.0.0")
doLast { println("${ver.get()} @ ${sha.get()}") }
}go deeper
Know that the requested tasks plus some properties/files form the key and changing them recomputes the graph.
Enumerate the key components and explain tracked reads via the providers APIs versus untracked System.getenv/getProperty.
Reason about cache hit-rate: stable invocations and tracked inputs maximize reuse; discuss how undeclared reads cause staleness.
Set conventions/lint so plugins and build logic always use tracked providers, protecting cache correctness across teams and CI.
## What the key is for The configuration cache must only be reused when re-running configuration would produce the **same** task graph. So the cache key is the union of all inputs that could change that graph. ## Components of the key 1. **The set of requested tasks** — `./gradlew build` and `./gradlew test` are different requests and get different entries. Different CLI flags that affect the build also vary the key. 2. **Build configuration inputs** — Gradle properties and `gradle.properties` values. 3. **System properties** read during configuration. 4. **Environment variables** read during configuration. 5. **Files** whose content is read during configuration. Gradle keeps a **separate entry per distinct task request**, so switching back and forth between two common invocations can each hit their own cached graph. ## How reads become inputs Gradle can only key on inputs it *knows about*. The way to make a read tracked is to use the **value provider APIs** instead of raw Java/Kotlin access: ```kotlin // Tracked: becomes a configuration cache input val ci = providers.environmentVariable("CI") val flag = providers.systemProperty("my.flag") val ver = providers.fileContents( layout.projectDirectory.file("version.txt") ).asText tasks.register("printEnv") { val ciVal = ci.orElse("false") doLast { println(ciVal.get()) } } ``` Using `System.getenv("CI")` or `System.getProperty(...)` **at configuration time** is an undeclared read: depending on Gradle version it is either flagged as a problem or captured without being tracked. The provider form records the value so a change invalidates the entry. ## What invalidates an entry - A different requested task set (→ different key, different entry). - A changed value of any tracked system property, env var, or Gradle property. - A changed content of any file read during configuration (build scripts, `gradle.properties`, files read via `fileContents`). - A changed Gradle version or changed build logic. When an entry is invalidated, Gradle logs that it is **calculating the task graph** again and stores a fresh entry. ## Why this matters Understanding the key explains the two classic surprises: (a) toggling `-Dsome.prop=...` on the command line legitimately misses the cache, and (b) reading inputs the untracked way can make the cache *stale* (reuse when it should not) or noisy with problems.
- Why does `./gradlew build` and `./gradlew test` each keep its own configuration cache entry?The requested task set is part of the cache key, so different task requests resolve to different keys and Gradle stores a distinct serialized graph per request.
- If a plugin calls `System.getenv("CI")` during configuration, what is the risk?It is an untracked read: the value isn't recorded as a cache input, so a change to CI won't invalidate the entry (staleness), and on recent Gradle it is reported as a configuration cache problem. Use `providers.environmentVariable("CI")` instead.
- Does changing a file that is only read at execution time invalidate the configuration cache?No. Only files read during the configuration phase are configuration cache inputs; execution-time file reads are task inputs handled by up-to-date checks / the build cache.
saying these in an interview costs you the question
- Saying the key is just the requested tasks — it also includes tracked props, env vars, and files.
- Claiming any project change invalidates it — only inputs read during configuration matter.
- Thinking raw System.getenv at config time is safely tracked — it is not.