skip to content

Build Cache

Reusing task outputs across builds and machines: enabling the cache, local versus remote backends, CI seeding, cacheable task requirements, and diagnosing misses. Interviewers ask because a shared cache is the biggest single CI win Gradle offers.

on this pageshow

explore

questions

30

What does it mean for a Gradle task to be cacheable, and what is the minimum required to make a task's outputs reusable from the build cache?

level: juniorimportance: must knowfreq 70%

answer

  1. key = hash of declared inputs
  2. @CacheableTask opt-in
  3. declare ALL inputs/outputs
  4. FROM-CACHE vs UP-TO-DATE
  5. undeclared input = silent wrong output

basics

~10 s

A cacheable task can store its outputs in the build cache and restore them later instead of re-running. It needs the @CacheableTask annotation and fully declared inputs and outputs.

solid answer

~40 s

A cacheable task is one whose outputs Gradle can store keyed by a hash of its inputs, then restore on a later build (or another machine) instead of executing. For a task to actually be cached, two things must hold: its type must opt in with `@CacheableTask` (or be enabled per-instance via `outputs.cacheIf {}`), and **all** inputs and outputs must be fully declared through annotations like `@Input`, `@InputFiles`, `@OutputFile`, etc. Gradle hashes those declared inputs into a cache key; if a matching entry exists, it copies the outputs from the cache and marks the task `FROM-CACHE`. Anything Gradle can't see — undeclared inputs, reads from absolute paths, system time — silently breaks correctness, so complete and accurate input/output declaration is the real prerequisite, not just the annotation.

code

kotlin · 12 lines
kotlin
@CacheableTask
abstract class Concat : DefaultTask() {
    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)
    abstract val inputs: ConfigurableFileCollection

    @get:OutputFile
    abstract val output: RegularFileProperty

    @TaskAction
    fun run() { /* ... */ }
}

go deeper

for a junior

Know that @CacheableTask plus declared inputs/outputs lets Gradle reuse results, and that FROM-CACHE means the task didn't run.

for a middle

Explain the cache key as a hash of declared inputs and why undeclared inputs cause silent wrong restores; distinguish from up-to-date.

for a senior

Discuss relocatability/normalization and path sensitivity so entries are reusable across machines, and judge which tasks are worth making cacheable.

for a principal

Frame caching correctness as an org-wide invariant — full input declaration discipline, normalization policy, and the cost of a poisoned shared cache.

## What "cacheable" means Gradle's **build cache** is a key-value store mapping a **cache key** (a hash of everything that affects a task's output) to the task's **output files**. When a task runs, Gradle can store its outputs under that key. On a later build — even on a different machine or CI agent — if the same key is computed, Gradle restores the outputs and reports the task as `FROM-CACHE` instead of executing it. This differs from **up-to-date** (incremental) checking, which only skips work within the *same* build directory. A task is **cacheable** only if it opts in. There are two ways: 1. The task **type** is annotated `@CacheableTask` — every instance is cacheable by default. 2. A specific instance opts in via `task.outputs.cacheIf { <predicate> }`. ## Why full input/output declaration is the real requirement The cache is only correct if the cache key captures **everything** that influences the outputs. Gradle builds the key from the task's **declared** inputs: - `@Input` — scalar/serializable properties (strings, ints, enums). - `@InputFile` / `@InputFiles` / `@InputDirectory` — file inputs (hashed by content). - `@OutputFile` / `@OutputFiles` / `@OutputDirectory` — declared outputs (what gets stored/restored). - `@Classpath` / `@CompileClasspath` — file collections with classpath normalization. If a task reads a file or property it never declares, that value won't be in the key. Two runs with different real inputs can then collide on the same key, and Gradle will restore **wrong** outputs — a silent correctness bug. So "making a task cacheable" is mostly about **declaring inputs/outputs completely and accurately**, not just adding an annotation. ## Relocatability and normalization For a cache entry produced on one machine to be reusable on another, the key must not depend on machine-specific details like absolute paths. Gradle achieves this with **input normalization**: file inputs are tracked by **relative** path and content, classpaths ignore ordering/timestamps, and you can further normalize (e.g. `normalization { runtimeClasspath { ignore '...' } }`). `@CacheableTask` signals that the task's inputs are declared in a way that's safe to relocate. ## Minimum checklist ```kotlin @CacheableTask abstract class GenerateThing : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val sources: ConfigurableFileCollection @get:Input abstract val version: Property<String> @get:OutputDirectory abstract val outputDir: DirectoryProperty } ``` The annotation makes the type opt in; the `@Input`/`@InputFiles`/`@OutputDirectory` declarations make the key correct and the result restorable.

  • What is the difference between a task reported as UP-TO-DATE and one reported as FROM-CACHE?
    UP-TO-DATE means inputs were unchanged since the last run in this same build directory, so Gradle skipped execution. FROM-CACHE means Gradle restored outputs from the build cache keyed by an input hash — it can apply across clean checkouts and other machines.
  • What happens if a task reads a file it never declares as an input?
    That file isn't part of the cache key, so two genuinely different runs can share a key and Gradle may restore stale or wrong outputs — a silent correctness bug. The fix is to declare it with the appropriate @Input* annotation.

Like memoizing a function: the cache is only correct if the function key includes every argument that affects the result. A hidden global read breaks it.

saying these in an interview costs you the question

  • Saying @CacheableTask alone makes caching safe, without mentioning complete input/output declaration.
  • Confusing build cache (cross-build/cross-machine) with up-to-date incremental checks (same build dir).
  • Claiming the cache key is based on output content rather than declared input content.

context

open as a page

How do you turn the Gradle build cache on, and what is the simplest way to make it the default for everyone on the project?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Set org.gradle.caching=true in the project's gradle.properties so it is on for every build and every developer, or pass --build-cache on a single command to enable it just for that run.

open as a page

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%

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.

open as a page

Where does Gradle's local build cache store its entries by default, and what does it actually hold?

level: juniorimportance: must knowfreq 55%

basics

~10 s

By default the local build cache lives in ~/.gradle/caches/build-cache-1. It stores the outputs of cacheable tasks keyed by a hash, so a later build can reuse them instead of re-running the task.

open as a page

Where in a Gradle project do you configure the push/pull split for the remote build cache, and at what point in the build lifecycle does that configuration take effect?

level: juniorimportance: must knowfreq 45%

basics

~20 s

In settings.gradle(.kts), inside the buildCache { remote<HttpBuildCache> { ... } } block. It takes effect very early — during settings evaluation, before any project is configured — because the cache must be ready before tasks run.

open as a page

How do you configure a remote HTTP build cache in Gradle, and where does that configuration live?

level: juniorimportance: must knowfreq 55%

basics

~10 s

In settings.gradle(.kts) inside a buildCache block, declare remote(HttpBuildCache) and set its url to your cache server endpoint. It must also be enabled, typically with --build-cache or org.gradle.caching=true.

open as a page

Why do cacheable tasks need input path sensitivity and normalization, and how do you configure them?

level: middleimportance: must knowfreq 55%

basics

~10 s

Without normalization, absolute paths and irrelevant file metadata leak into the cache key, so entries miss across machines. @PathSensitive(RELATIVE) and classpath normalization make keys stable and relocatable.

open as a page

What is the buildCache {} block in settings.gradle.kts for, and how does it relate to org.gradle.caching=true?

level: middleimportance: must knowfreq 50%

basics

~10 s

org.gradle.caching=true is the on/off switch; the buildCache {} block in settings.gradle.kts configures the cache backends (local and remote). The switch decides whether caching runs; the block decides where outputs are stored.

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

How do you configure the local build cache — change its directory and tune retention — and where does that configuration go?

level: middleimportance: must knowfreq 50%

basics

~10 s

In settings.gradle(.kts) use the buildCache { local { ... } } block. Set directory to relocate it and removeUnusedEntriesAfterDays to control how long unused entries survive (default 7).

open as a page

In a team that uses a shared remote build cache, why is it common to let only CI seed jobs push to the cache while developers pull read-only? How do you configure that split?

level: middleimportance: must knowfreq 55%

basics

~20 s

CI builds run in clean, trusted environments, so their outputs are reliable cache entries. Developers' machines vary and could poison the cache, so they only read. You set isPush = true on CI and false for developers in the remote(HttpBuildCache) block.

open as a page

How do you enable pushing to a remote HTTP build cache and supply credentials securely?

level: middleimportance: must knowfreq 60%

basics

~10 s

Set push = true on the remote(HttpBuildCache) and provide credentials { username = ...; password = ... }. Read the secrets from environment variables or gradle.properties rather than hardcoding them in the settings file.

open as a page

What is outputs.cacheIf {} and when would you use it instead of (or alongside) @CacheableTask?

level: middleimportance: should knowfreq 45%

basics

~20 s

outputs.cacheIf {} adds a runtime predicate that enables caching for a specific task instance only when it returns true. Use it when caching is worthwhile only under certain conditions, or to enable caching without owning the task type.

open as a page

Which input/output annotations does Gradle use to build a cacheable task's key, and what does @Internal mean for caching?

level: middleimportance: should knowfreq 40%

basics

~10 s

@Input, @InputFile(s)/@InputDirectory, @Classpath, @Nested feed the cache key; @OutputFile(s)/@OutputDirectory declare what's stored. @Internal marks a property that is neither — it's excluded from the key.

open as a page

Enabling the build cache and Gradle's incremental/up-to-date checking are sometimes confused. After enabling caching, why might a 'clean' build still be fast, and how is that different from up-to-date checks?

level: middleimportance: should knowfreq 40%

basics

~20 s

Up-to-date checks skip a task only if its outputs already exist in the build directory, so clean wipes them. The build cache, once enabled, restores outputs from a separate store keyed by inputs, so even after clean a matching task is unpacked FROM-CACHE instead of re-run.

open as a page

If gradle.properties has org.gradle.caching=true but a build is invoked with --no-build-cache, what happens, and how do the command-line flag, system property, and gradle.properties relate?

level: middleimportance: should knowfreq 45%

basics

~10 s

The command-line flag wins. --no-build-cache disables caching for that run despite the property. The --build-cache/--no-build-cache flag overrides org.gradle.caching, which overrides the default (off).

open as a page

How does the local build cache control its size over time, and what are the trade-offs when tuning retention?

level: middleimportance: should knowfreq 35%

basics

~10 s

Gradle periodically cleans the local cache, removing entries not read within removeUnusedEntriesAfterDays (default 7). A longer window keeps more cache hits but uses more disk; a shorter window saves disk but loses older entries.

open as a page

On CI you have both PR builds and a main-branch build. How should each participate in the remote cache, and how do you express that split so PR builds don't degrade the shared cache?

level: middleimportance: should knowfreq 30%

basics

~20 s

Make the main-branch (or release) job the only pusher and let PR builds pull read-only. Drive it from an invocation flag/property the seed job passes, e.g. isPush = isMainBranch, while every job keeps isEnabled = true to read.

open as a page

At the HTTP level, how does Gradle interact with a remote HttpBuildCache server when reading and storing entries?

level: middleimportance: should knowfreq 35%

basics

~20 s

Gradle appends the cache key to the configured URL. It does a GET to fetch an entry (200 = hit, 404 = miss) and a PUT to store one when push is enabled. The body is the packed task-output archive.

open as a page

A custom cacheable task gets cache hits locally but never on CI. As a senior engineer, how do you reason about and fix the cause?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Local hits but cross-machine misses almost always mean the cache key isn't relocatable: absolute paths, machine-specific input content, or timestamps leak in. Fix with relative path sensitivity and input normalization so keys match across machines.

open as a page

You are introducing the build cache to a team and CI. How would you roll out enabling it so it is safe, consistent, and verifiable across developers and pipelines?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Commit org.gradle.caching=true to gradle.properties so every developer and CI agent picks it up from version control, then verify with --info/build scans that tasks report FROM-CACHE, keeping --no-build-cache available as a per-run escape hatch.

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

Why might you relocate the local build cache directory, and what should you watch out for when you do?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Set local { directory = ... } to move the cache — e.g. onto a fast SSD, a project-local path, or a CI path you can snapshot. Watch that the path is writable, fast, and that sharing it across users/agents stays consistent.

open as a page

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%

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.

open as a page

Your shared HTTP cache started serving wrong outputs after a developer's machine pushed bad entries. How do you prevent this going forward, including at the credential/server level?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Make developers pull read-only (isPush = false) and reserve push for trusted CI. Enforce it at the server with separate credentials: developers get a read-only token, only the CI seed job holds a write-capable token. Then purge poisoned entries.

open as a page

How would you gate isPush and isEnabled on Gradle's startParameter rather than reading environment variables directly inside the buildCache block? Why prefer that?

level: seniorimportance: should knowfreq 35%

basics

~20 s

settings.startParameter exposes the actual invocation flags (e.g. isOffline, task names, --build-cache). You read those inside buildCache {} to decide isPush/isEnabled, so the policy reacts to how the build was launched rather than to ambient env state.

open as a page

What practical concerns arise when running a remote HttpBuildCache in production — TLS, proxies, and resilience — and how do you address them in the DSL?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Use HTTPS and, if the cert is self-signed, allow insecure protocol or trust the cert. Honor JVM proxy settings. Because cache I/O is best-effort, an unreachable server slows but never breaks builds; tune for low latency near CI.

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

When would you choose the plain built-in HttpBuildCache versus a managed remote cache like Develocity, and what governance tradeoffs come with running your own?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

The built-in HttpBuildCache is a simple blob store you self-host — cheap and dependency-free but no auth granularity, eviction policy, or analytics. Develocity adds management, fine-grained access, and build observability at a licensing cost. Choose by scale and need for insight.

open as a page