What does the @CacheableTask annotation do, and what is the difference between marking a task cacheable and the build cache simply being enabled?
answer
- type-level opt-in
- key = hash of inputs
- superset of up-to-date
- enable != mark cacheable
- cacheIf / doNotCacheIf
basics
~10 s@CacheableTask on a task type opts that type into the build cache, so its outputs can be stored and reused. Enabling the build cache alone does nothing unless task types are marked cacheable.
solid answer
~40 sThe build cache is a key-value store of task outputs keyed by a hash of the task's inputs. Enabling it (`--build-cache` or `org.gradle.caching=true`) turns the mechanism on, but a task only participates if its **type** is annotated `@CacheableTask`. By default custom and most built-in tasks are NOT cacheable — they're only up-to-date checked locally. Marking a task `@CacheableTask` tells Gradle: this task's inputs are fully and correctly declared and its outputs are reproducible, so it's safe to store outputs under a cache key and reuse them — even across machines via a remote cache. You can also flip individual instances on/off with `task.outputs.cacheIf { ... }` / `doNotCacheIf { }`.
code
kotlin · 17 lines@CacheableTask
abstract class GenerateReport : DefaultTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val sources: ConfigurableFileCollection
@get:OutputFile
abstract val report: RegularFileProperty
@TaskAction
fun run() { /* produce report from sources */ }
}
// per-instance refinement
tasks.named<GenerateReport>("generateReport") {
outputs.cacheIf { sources.files.size < 10_000 }
}go deeper
Know @CacheableTask is a class-level opt-in and that enabling the cache is a separate switch.
Explain key = hash of inputs, cache as a superset of up-to-date, and per-instance cacheIf/doNotCacheIf.
Discuss why opt-in exists (correctness/determinism guarantees) and which built-ins are/aren't cacheable and why.
Frame local vs remote cache strategy, CI hit-rate, and the org-wide correctness bar before broadly enabling caching.
## What the build cache is Gradle's **build cache** is a key-value store. The *key* is a hash computed from everything that can affect a task's outputs (its inputs); the *value* is a packaged copy of that task's output files. When Gradle is about to run a task, it computes the cache key; on a hit it unpacks the stored outputs instead of executing the task. This is a superset of the **up-to-date** (incremental) check: up-to-date only avoids re-running when *this build directory* already holds the right outputs, whereas the cache can supply outputs produced by an *earlier build or a different machine* (with a **remote** cache). ## Enabling vs. marking cacheable — two separate switches 1. **Enabling the cache** turns the machinery on: `--build-cache` on the CLI, or `org.gradle.caching=true` in `gradle.properties`. This alone caches *nothing extra* unless tasks opt in. 2. **Marking a task type cacheable** is `@CacheableTask` on the task *class*. Without it, even with the cache enabled, the task is never stored or loaded from the cache — it only benefits from the in-place up-to-date check. ```kotlin @CacheableTask abstract class GenerateReport : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val sources: ConfigurableFileCollection @get:OutputFile abstract val report: RegularFileProperty @TaskAction fun run() { /* ... */ } } ``` ## Why opt-in? Caching is only safe when a task's inputs are **completely and correctly declared** and its outputs are **deterministic** (no embedded timestamps/absolute paths, same inputs ⇒ same bytes). Gradle can't prove this, so it makes you assert it by annotating the type. A task with an undeclared input (e.g. it reads an env var or a file it never registered as `@InputFile`) would otherwise produce stale cache hits. ## Per-instance control `@CacheableTask` is a *type-level* default. You can refine per instance: - `outputs.cacheIf { spec }` — only cache when the predicate holds. - `outputs.doNotCacheIf("reason") { spec }` — exclude when a predicate holds. All `cacheIf` must be true and no `doNotCacheIf` true for the instance to be cached. ## Built-ins Many core tasks (`JavaCompile`, `Test`, `Checkstyle`, …) are already `@CacheableTask`. Tasks like `Copy`/`Jar` are intentionally *not* cacheable because packing/unpacking from the cache is rarely cheaper than just doing the copy.
- If I add org.gradle.caching=true but my custom task isn't @CacheableTask, what happens?Nothing for that task — it still runs (or is up-to-date) but is never stored to or loaded from the cache. Only its inputs/outputs are tracked for the in-place up-to-date check.
- Where does the cache live by default and how does a remote cache differ?A local directory cache (under the Gradle user home) is on by default once caching is enabled. A remote cache (e.g. an HTTP/Develocity cache) is shared across machines/CI, letting one machine reuse another's outputs.
Enabling the cache installs the shared fridge; @CacheableTask is each cook labelling a dish 'safe to share' — an unlabelled dish stays in their own kitchen even though the fridge is on.
saying these in an interview costs you the question
- Thinking org.gradle.caching=true alone makes all tasks cacheable.
- Claiming @CacheableTask is the same as @UpToDate or replaces incremental checks.
- Marking a task cacheable without verifying its outputs are deterministic.