What is the difference between `outputs.cacheIf {}` and `outputs.upToDateWhen {}`?
answer
- upToDateWhen = local skip
- cacheIf = build-cache eligibility
- clean: not up-to-date but FROM-CACHE
- doNotCacheIf(reason){}
- cacheIf needs @CacheableTask
basics
~10 supToDateWhen controls whether a task is skipped locally (incremental up-to-date check). cacheIf controls whether a task's outputs may be stored in / loaded from the build cache. They are independent decisions.
solid answer
~50 sBoth are predicates on a task's `outputs`, but they govern different mechanisms: - **`outputs.upToDateWhen { spec }`** — the *local incremental* decision. If the spec returns true and input/output fingerprints are unchanged, the task is `UP-TO-DATE` and skipped this build. - **`outputs.cacheIf { spec }`** — *build-cache* eligibility. If true (and the task is cacheable), Gradle may store outputs keyed by an input hash and reload them later — even on a different machine or after `clean`. They compose: a clean checkout can't be up-to-date (no prior outputs) but can still be `FROM-CACHE`. You typically enable `cacheIf` for deterministic, relocatable work, and use `upToDateWhen { false }` for untrackable side effects. There's also `outputs.doNotCacheIf { reason, spec }` to exclude specific cases with a human-readable reason. `cacheIf` requires `@CacheableTask` (or being a built-in cacheable task) to actually take effect.
code
kotlin · 7 linestasks.register<MyTask>("transform") {
// Local incremental skip when nothing changed
outputs.upToDateWhen { true } // default; shown for contrast
// Allow cache reuse, but never for non-deterministic runs
outputs.cacheIf { true }
outputs.doNotCacheIf("depends on wall clock") { usesTimestamp }
}go deeper
Know that one is about local skipping and the other about the build cache, even if details are fuzzy.
Clearly separate the two mechanisms and explain the clean-checkout case (not up-to-date but FROM-CACHE).
Discuss composition (cacheIf OR, doNotCacheIf veto), the @CacheableTask requirement, and determinism/relocatability prerequisites.
Define org policy for which task types are cacheable, the local-vs-remote cache topology, and how to audit cache misses via build scans.
## Two independent skip mechanisms Gradle can avoid re-doing work in two different ways: 1. **Up-to-date checking (incremental builds)** — purely local. Before running, Gradle compares the current fingerprint of declared inputs/outputs against the previous run on *this* machine. Match ⇒ `UP-TO-DATE`, skipped. 2. **Build cache** — a key→outputs store (local `~/.gradle/caches/build-cache-1` and/or a remote node). The key is a hash of the task type, inputs, and classpath. A cache hit reports `FROM-CACHE` and copies outputs in, even after `clean` or on a fresh machine/CI agent. ## The two predicates map to those mechanisms ```kotlin tasks.named<JavaCompile>("compileJava") { outputs.cacheIf { true } // eligible for the build cache outputs.upToDateWhen { true } // (default) participate in up-to-date checks } ``` - `upToDateWhen` ⇒ may I **skip locally**? - `cacheIf` ⇒ may I **store/reuse via the cache**? ## Why both exist — the matrix | Situation | up-to-date? | from-cache? | |---|---|---| | Rebuild, nothing changed | yes (skipped) | n/a | | After `clean`, inputs unchanged | no (no prior outputs) | yes (cache hit) | | Fresh CI agent, inputs match | no | yes | | Untrackable side effect (`upToDateWhen{false}`) | no | depends on cacheIf | This is the key insight: `upToDateWhen { false }` does **not** by itself stop cache reuse. To force genuinely-every-build work, also keep the task non-cacheable (don't add `@CacheableTask`/`cacheIf { true }`, or use `doNotCacheIf`). ## doNotCacheIf ```kotlin outputs.doNotCacheIf("output is non-deterministic") { someCondition } ``` `cacheIf` is OR-ed across registrations and ANDed with the negation of `doNotCacheIf`; the string reason shows up in build scans, which makes it the preferred way to *exclude* edge cases. ## Practical guidance Enable caching only for tasks that are deterministic and relocatable (path-independent). Use `upToDateWhen` for the cheap local skip, and reserve `upToDateWhen { false }` for side-effecting tasks.
- If a task is `upToDateWhen { false }` but also `cacheIf { true }`, what happens after a clean build with unchanged inputs?It's reported NOT up-to-date (no prior local outputs / predicate forces it), but if there's a cache hit it can still come back as FROM-CACHE. To guarantee real work, also make it non-cacheable.
- Why use `doNotCacheIf` instead of `cacheIf { false }`?`doNotCacheIf` takes a human-readable reason that surfaces in build scans/diagnostics, and it composes cleanly with multiple `cacheIf` rules (OR for cacheIf, the doNotCacheIf vetoes), making intent and exclusions explicit.
Up-to-date is like 'I already did this on my desk, skip it.' The build cache is a shared library of finished work: even on a brand-new desk you can borrow someone's completed result instead of redoing it.
saying these in an interview costs you the question
- Saying `upToDateWhen { false }` disables the build cache — it does not; the two are independent.
- Believing `cacheIf { true }` alone caches any task — the task must be `@CacheableTask` (or a built-in cacheable type) for it to take effect.