skip to content

Compare `onlyIf {}` with `outputs.upToDateWhen {}`. When does each cause a task to be skipped, and how do they appear in the build output?

level: middleimportance: should knowfreq 33%

answer

  1. onlyIf = run or not (SKIPPED)
  2. upToDateWhen = already done? (UP-TO-DATE)
  3. onlyIf{false}=skip, upToDateWhen{false}=run
  4. onlyIf evaluated first
  5. different output labels

basics

~20 s

onlyIf { spec } decides whether the task runs at all — false means SKIPPED. outputs.upToDateWhen { spec } decides whether a task that would run is already up-to-date — true (with matching fingerprints) means UP-TO-DATE. Different decisions, different labels.

solid answer

~50 s

Both take a `Spec<Task>`, but they answer different questions: - **`onlyIf { spec }`** — *should this task execute?* If the spec is false the task is **SKIPPED** entirely (its actions never run). Use it to disable a task based on conditions (a flag, OS, property). - **`outputs.upToDateWhen { spec }`** — *is this task already done?* It contributes to the up-to-date check; when true and input/output fingerprints match, the task is **UP-TO-DATE** and skipped because there's nothing to do. So `onlyIf { false }` and `upToDateWhen { false }` are opposites in spirit: `onlyIf { false }` means 'don't run, it's irrelevant'; `upToDateWhen { false }` means 'definitely run, it's never done.' They also differ in output labels (`SKIPPED` vs `UP-TO-DATE`) and in evaluation: `onlyIf` short-circuits before up-to-date checking. A common interview trap is conflating the two — `onlyIf` gates execution, `upToDateWhen` gates incremental skipping.

code

kotlin · 5 lines
kotlin
tasks.register("publishDocs") {
    onlyIf { project.hasProperty("release") } // SKIPPED unless -Prelease
    outputs.upToDateWhen { false }            // if it runs, never up-to-date
    doLast { /* publish */ }
}

go deeper

for a junior

Know onlyIf decides whether to run and upToDateWhen decides whether it's already done.

for a middle

Explain evaluation order, the SKIPPED-vs-UP-TO-DATE labels, and the opposite meanings of the { false } forms.

for a senior

Map each to its intent (relevance vs freshness) and warn against misusing one for the other.

for a principal

Define conventions: gate environment/flag relevance with onlyIf, model freshness with declared inputs/outputs, reserve upToDateWhen{false} for untrackable side effects.

## Two gates, two questions When Gradle reaches a task in the graph it asks, in order: 1. **`onlyIf` predicates** — should I run this at all? If any `onlyIf` returns false, the task is **SKIPPED**; its actions never execute. (Multiple `onlyIf` calls are AND-ed.) 2. **Up-to-date check** — if it should run, is it already up-to-date? Gradle compares input/output fingerprints AND evaluates `outputs.upToDateWhen` predicates. If everything matches and predicates are true, the task is **UP-TO-DATE** and skipped. 3. Otherwise the task **executes** its actions. ## The four corners | Construct | false ⇒ | true ⇒ | |---|---|---| | `onlyIf { ... }` | SKIPPED (don't run) | proceed to up-to-date check | | `outputs.upToDateWhen { ... }` | not up-to-date ⇒ likely runs | up-to-date if fingerprints also match | Note the asymmetry: `onlyIf { false }` = skip; `upToDateWhen { false }` = run. ## Output labels - `SKIPPED` — excluded by `onlyIf` (or `enabled = false`). - `UP-TO-DATE` — passed up-to-date check. - `NO-SOURCE` — declared inputs exist but none are present. - `FROM-CACHE` — outputs pulled from the build cache. - `(executed)` — ran its actions. ## Example ```kotlin tasks.register("publishDocs") { onlyIf { project.hasProperty("release") } // only on release builds outputs.upToDateWhen { false } // when it does run, always run } ``` On a non-release build: **SKIPPED**. On a release build: it always executes (never UP-TO-DATE). ## Why it matters Use `onlyIf` for *relevance* (environment, flags, platform) and `upToDateWhen` for *freshness* (is the work already done). Mixing them — e.g., using `upToDateWhen { false }` to disable a task — produces wasted execution; using `onlyIf` to model freshness throws away incremental benefits.

  • Which is evaluated first, onlyIf or the up-to-date check?
    `onlyIf` is evaluated first. If it returns false the task is SKIPPED and Gradle never performs the up-to-date check or runs the actions.
  • How do you disable a task unconditionally without a predicate?
    Set `enabled = false` on the task, which makes it SKIPPED like a false `onlyIf`. `onlyIf` is for conditional disabling; `enabled` is the static switch.

saying these in an interview costs you the question

  • Using `upToDateWhen { false }` to 'disable' a task — it forces the task to run, not skip.
  • Saying both produce the same UP-TO-DATE label — `onlyIf` produces SKIPPED.

context