skip to content

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

level: middleimportance: should knowfreq 45%

answer

  1. per-instance conditional switch
  2. cacheIf(reason){spec} AND-ed
  3. doNotCacheIf vetoes with reason
  4. enable type you don't own
  5. doesn't replace input declaration

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.

solid answer

~50 s

`@CacheableTask` opts a task **type** in to caching for every instance. `task.outputs.cacheIf { spec }` is the **per-instance, conditional** lever: it registers a predicate evaluated at execution time, and the task is cached only if the predicate returns true (and all `cacheIf` predicates are true, and no `doNotCacheIf` vetoes). You use it when (a) you don't own the task type but want to enable caching on a particular instance, or (b) caching is only beneficial or only correct under some condition — e.g. cache a test task only when it isn't running with a flaky/external dependency, or skip caching tiny inputs where store/restore overhead exceeds the saving. There's a companion `outputs.doNotCacheIf("reason") { spec }` that vetoes caching with a human-readable reason shown in diagnostics. Note the predicate gates *whether* caching happens; you still need complete, normalized input/output declarations for the result to be correct. `cacheIf` doesn't relax those requirements.

code

kotlin · 8 lines
kotlin
tasks.named<Test>("integrationTest") {
    outputs.cacheIf("deterministic only when offline") {
        !project.hasProperty("useLiveServices")
    }
    outputs.doNotCacheIf("trivial input set") {
        inputs.files.files.size < 3
    }
}

go deeper

for a junior

Know cacheIf {} is a true/false switch that turns caching on for a specific task under a condition.

for a middle

Explain cacheIf AND semantics, doNotCacheIf veto with reason, and when to use it vs the type annotation.

for a senior

Weigh store/restore overhead, encode conditional correctness, and apply it to third-party tasks you can't annotate.

for a principal

Set policy on which conditional-caching predicates are sanctioned and how reasons are surfaced for fleet-wide diagnosis.

## Two levers, different scope Gradle gives you two ways to make a task cacheable: - **`@CacheableTask`** on the task *class* — declarative, applies to *all* instances of that type, and is the right home for a reusable plugin's task. (Authoring the type itself is a separate concern.) - **`outputs.cacheIf {}`** on a task *instance* — imperative and conditional, applied in a build script to one configured task. They compose: a task is cached only if it is enabled *and* every registered `cacheIf` predicate is true *and* no `doNotCacheIf` predicate vetoes. ## The API ```kotlin tasks.named<Test>("integrationTest") { outputs.cacheIf("only cache when not hitting live services") { !project.hasProperty("useLiveServices") } outputs.doNotCacheIf("inputs too small to be worth caching") { inputs.files.files.size < 3 } } ``` - `cacheIf(reason) { spec }` — caching allowed only if the spec returns true. Multiple `cacheIf` calls are AND-ed. - `doNotCacheIf(reason) { spec }` — if the spec returns true, caching is vetoed regardless of `cacheIf`. The `reason` string surfaces in build scans / `--info` to explain misses. The predicate receives the task and is evaluated **at execution time**, so it can depend on resolved inputs. ## When to use it - **You don't own the type.** A built-in or third-party task isn't `@CacheableTask`, but for your usage its inputs/outputs are fully declared and relocatable — you can opt that instance in. - **Conditional worth.** Store/restore has overhead; for tasks with trivial inputs or already-near-instant execution, gate with `doNotCacheIf` so you don't pay cache cost for no gain. - **Conditional correctness/safety.** Cache a test or generation task only in configurations where the result is deterministic (e.g. exclude runs that touch external state). ## What it does NOT do `cacheIf` only decides *whether* to attempt caching. It does **not** declare inputs/outputs, doesn't add normalization, and won't make an under-declared task safe. If declarations are incomplete, enabling caching via `cacheIf` produces the same silent-wrong-output risk as `@CacheableTask` would. Treat it as the on/off (conditional) switch layered on top of correct declarations. ## Built-in tasks Many Gradle built-ins (e.g. `JavaCompile`, `Test`) are already `@CacheableTask` and cache by default once the cache is enabled, so you rarely need `cacheIf` for them — it's mainly for custom or third-party tasks and conditional policies.

  • If both a cacheIf returning true and a doNotCacheIf returning true are registered, is the task cached?
    No. doNotCacheIf is a veto: if any doNotCacheIf predicate returns true, caching is disabled regardless of cacheIf. The reason string explains the miss in diagnostics.
  • Does cacheIf relax the need to declare inputs and outputs?
    No. cacheIf only decides whether caching is attempted. Inputs/outputs must still be fully declared and normalized, or you risk restoring incorrect outputs — exactly as with @CacheableTask.

saying these in an interview costs you the question

  • Claiming cacheIf can make an under-declared task safe to cache.
  • Forgetting doNotCacheIf overrides cacheIf (veto semantics).
  • Using cacheIf when the right place is @CacheableTask on a type you actually own.

context