What is the difference between gating a task with onlyIf {} versus a configuration-time if-block that conditionally registers or wires the task?
answer
- config-time = task may not exist
- onlyIf = task exists but SKIPPED
- config sees only config-known values
- dependents preserved with onlyIf
- onlyIf body runs at execution
basics
~20 sA configuration-time if-block decides whether the task is even created/wired while scripts are evaluated. onlyIf decides at execution time whether an existing task runs. Configuration-time conditions can use only configuration-known values; onlyIf can use values known later.
solid answer
~50 sA **configuration-time conditional** is a plain `if` in the build script: it runs during the configuration phase and decides whether to `tasks.register(...)`, add a dependency, or set a property. If the condition is false, the task may not exist at all (or won't be wired), so it never appears in the graph. **`onlyIf {}`** keeps the task in the graph and defers the decision to **execution time** — the task is requested and shows up, but its actions are skipped (`SKIPPED`) if the predicate is false. Key consequences: (1) configuration-time conditions can only see values known at configuration; `onlyIf` can read state produced during configuration or by upstream tasks. (2) A configuration-time exclusion removes the task from `gradle tasks` / the graph; `onlyIf` does not — the task is visible and reported SKIPPED. (3) With lazy configuration, prefer keeping conditions out of eager configuration; `onlyIf` is naturally lazy because its body runs at execution.
code
kotlin · 7 lines// Config-time: task absent when flag missing
if (project.hasProperty("ci")) tasks.register("slowCheck")
// Execution-time: task present, skipped when flag missing
tasks.register("slowCheck2") {
onlyIf { project.hasProperty("ci") }
}go deeper
Know that onlyIf runs at execution and a config-time if runs while scripts are read.
Explain visibility/graph differences and that onlyIf preserves dependents.
Reason about lazy configuration and dependency wiring when choosing between the two.
Advise teams on convention: prefer onlyIf for runtime toggles to avoid graph fragility; reserve config-time guards for genuine existence decisions.
## Two phases, two kinds of conditionals Gradle builds run in phases: **initialization → configuration → execution**. Where you put a condition changes its meaning. ### Configuration-time conditional A bare `if` in a build script runs while Gradle is configuring the project: ```kotlin if (project.hasProperty("enablePublish")) { tasks.register("publish") { doLast { /* ... */ } } } ``` If the property is absent, the `publish` task is **never created** — it won't appear in `gradle tasks`, can't be depended on, and isn't in the task graph. The decision is final and based only on what's known at configuration. ### Execution-time gate (`onlyIf`) ```kotlin tasks.register("publish") { onlyIf { project.hasProperty("enablePublish") } doLast { /* ... */ } } ``` Here the task **always exists** and is wired into the graph. At execution Gradle evaluates the predicate; if false the task is `SKIPPED` but still visible. ## When each is right - The task **should not exist** under some condition (different plugin applied, platform mismatch) → configuration-time registration guard. - The task **exists but should sometimes no-op** based on a runtime flag, branch, or upstream result → `onlyIf {}`. - You need to branch on something **not yet known** at configuration (e.g. content produced by an earlier task) → `onlyIf {}`, because its body runs later. ## Visibility and dependents If task B `dependsOn` A and A is excluded at configuration time, the wiring may break or B loses its dependency. If A uses `onlyIf` instead, B's dependency on A is preserved; A simply runs as SKIPPED, and B still runs. This is often the safer default when other tasks depend on the gated task. ## Lazy configuration note Modern Gradle favors lazy APIs (`tasks.register`, `Provider`). A configuration-time `if` can force eager evaluation of values; an `onlyIf` predicate defers evaluation to execution, which composes better with `Provider`/`Property` graphs.
- Task B dependsOn A. You want A to sometimes no-op without breaking B. Config-if or onlyIf?onlyIf — it keeps A in the graph and preserves B's dependency; A just runs SKIPPED.
- Why can onlyIf branch on values a config-time if cannot?Its predicate runs at execution, after configuration and after upstream tasks, so later-known state is available.
saying these in an interview costs you the question
- Saying both approaches leave the task in the graph — config-time exclusion can remove it entirely.
- Using a config-time if for something that depends on runtime/upstream values.