skip to content

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

level: middleimportance: must knowfreq 60%

answer

  1. type + classpath of implementation
  2. doFirst/doLast also implementation
  3. @Input values hashed
  4. input files via normalization
  5. output property NAMES not content

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.

solid answer

~50 s

Gradle builds the cache key by hashing several components into one identifier: - **Task implementation** — the class name plus the hash of the classpath that loaded it (so a plugin upgrade invalidates entries). - **Task action implementations** — any extra `doFirst`/`doLast` action classes and their classpath. - **Input value properties** — each `@Input` value, serialized and hashed. - **Input files** — each `@InputFile`/`@InputFiles`/`@Classpath` content, hashed per the declared *normalization* (path-sensitivity, classpath normalization, etc.). - **Output property names** — the *names* of `@OutputFile`/`@OutputDirectory` properties (not their content). These are combined into a single key. If any component differs — a changed source file, a new compiler version on the classpath, a different `@Input` flag — the key changes and you get a miss. Crucially, *undeclared* inputs (env vars, absolute paths read at runtime, system clock) don't enter the key, which causes either false hits or non-reproducible outputs. Correct normalization and complete input declaration are what make keys stable and correct.

code

kotlin · 10 lines
kotlin
@CacheableTask
abstract class StampTask : DefaultTask() {
    @get:Input abstract val version: Property<String>

    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)
    abstract val sources: ConfigurableFileCollection

    @get:OutputDirectory abstract val outDir: DirectoryProperty
}

go deeper

for a junior

Know that the key hashes inputs and the task type; identical inputs give a hit.

for a middle

Enumerate the components (implementation, actions, input values, input files via normalization, output names) and what is excluded.

for a senior

Explain how normalization and undeclared inputs cause false hits or non-reproducibility, and how to inspect the key.

for a principal

Tie key composition to reproducible-build guarantees and policies that forbid undeclared inputs across the build fleet.

## The cache key is content-addressing for tasks A cacheable Gradle task is treated as a pure function: *outputs = f(inputs)*. The **build-cache key** is a hash that uniquely identifies one (function, inputs) pair. If two builds compute the same key, their outputs are interchangeable, so Gradle can store and restore them. ## What gets hashed 1. **Task type / implementation identity** — the task class plus a hash of the classloader's classpath that defined it. Upgrading the plugin or the Gradle distribution changes this, deliberately invalidating old entries (the bytecode that produces outputs changed). 2. **Additional action implementations** — ad-hoc `doFirst {}` / `doLast {}` closures are *also* implementation; their captured classpath hashes feed the key. This is why adding an inline action can silently bust caching. 3. **Input properties** (`@Input`) — scalar/serializable values, hashed. 4. **Input files** (`@InputFile`, `@InputFiles`, `@InputDirectory`, `@Classpath`, `@CompileClasspath`) — file *content* hashed, but interpreted through the declared **normalization**: - `@PathSensitive(RELATIVE|NAME_ONLY|NONE)` controls whether file paths affect the key. - `@Classpath` / `@CompileClasspath` ignore order/timestamps inside jars and (for compile classpath) non-ABI changes. 5. **Output property names** — the *identifiers* of declared outputs, so Gradle knows which directories to pack/unpack. Output *content* is not in the key (it's the result, not an input). ## What is NOT in the key (the danger zone) Anything you don't declare: environment variables, `System.getProperty` reads inside the action, absolute machine paths, network state, current time. If the task's real behavior depends on these, the key won't reflect it — leading to **false cache hits** (wrong outputs restored) or **non-cacheable nondeterminism** (outputs differ run-to-run, so keys never match). ## Inspecting the key Run with `-Dorg.gradle.caching.debug=true`. Gradle prints, per task, each hashed component and the final key — letting you diff two builds to find the one input that drifted. ```bash ./gradlew :app:compileJava --build-cache -Dorg.gradle.caching.debug=true # > Appending implementation to build cache key: ... # > Appending inputPropertyHash for 'options.compilerArgs' to ... # > Build cache key for task ':app:compileJava' is 8f3c... ``` Diffing this output between a hit and a miss is the single most effective cache-miss diagnosis technique.

  • Why does adding a doLast { } closure to a cacheable task sometimes cause misses?
    Closures are action implementations; their captured class/classpath hash feeds the key. A change there alters the key even if your inputs are identical.
  • Why are output property names in the key but not output content?
    Outputs are the result of the function, not an input to it. Gradle needs the names to know which directories to pack/restore, but their content is what gets stored, not hashed into the key.
  • How does @Classpath normalization help cache stability?
    It ignores jar entry order and timestamps and only considers relevant content, so cosmetic repackaging of a dependency jar doesn't bust the key.

saying these in an interview costs you the question

  • Saying output file content is part of the key
  • Forgetting that the task implementation/classpath is hashed
  • Believing undeclared env vars or absolute paths enter the key

context