How does Gradle decide a task is UP-TO-DATE during execution, and what makes it re-run?
answer
- snapshot inputs + outputs
- match previous fingerprint => skip
- outputs must still exist
- no outputs => always runs
- --rerun-tasks / upToDateWhen{false}
basics
~20 sBefore running a task, Gradle snapshots its declared inputs and outputs. If both match the previous run (and outputs still exist), it skips the action and reports UP-TO-DATE. Any change to an input, output, or the task's code makes it re-run.
solid answer
~40 sGradle's **incremental build** relies on each task declaring its **inputs** (`@Input`, `@InputFile(s)`, `@InputDirectory`, `@Classpath`) and **outputs** (`@OutputFile(s)`, `@OutputDirectory`). At execution time Gradle fingerprints these and compares against the snapshot from the last run, stored in `.gradle`. A task is reported **UP-TO-DATE** (and its action skipped) only if: all input fingerprints match, all output fingerprints match, the outputs still exist on disk, and the task's own implementation (class + actions) is unchanged. If anything differs — an input file's content/hash, an `@Input` property value, a new task action, or a manually deleted output — Gradle re-executes. Tasks with **no declared outputs** can never be UP-TO-DATE and always run. You can force re-execution with `--rerun-tasks` or `outputs.upToDateWhen { false }`.
code
kotlin · 9 linestasks.register("generate") {
val src = layout.projectDirectory.file("in.txt")
val dst = layout.buildDirectory.file("out.txt")
inputs.file(src)
inputs.property("version", project.version.toString())
outputs.file(dst)
doLast { dst.get().asFile.writeText(src.asFile.readText()) }
}
// Re-run: gradle generate --rerun-tasks (ignores up-to-date state)go deeper
Say Gradle skips a task when its inputs and outputs are unchanged, showing UP-TO-DATE.
Explain input/output declarations, fingerprinting, the existence-of-outputs check, and what forces re-run.
Discuss correctness implications of undeclared inputs, implementation-change detection, and the relationship to the build cache and NO-SOURCE.
Reason about incremental-build hygiene at scale: enforcing properly declared inputs/outputs across many custom tasks to keep builds correct and cacheable.
## Why up-to-date checking exists Most tasks transform **inputs** into **outputs** (sources → class files, classes → jar). If neither side changed, re-doing the work is wasted time. Gradle's **incremental build** skips such tasks, reporting `UP-TO-DATE`. ## What Gradle tracks A task must *declare* what it consumes and produces, typically via annotations on a typed task: ```kotlin abstract class Stamp : DefaultTask() { @get:InputFile abstract val source: RegularFileProperty @get:Input abstract val version: Property<String> @get:OutputFile abstract val out: RegularFileProperty @TaskAction fun run() { out.get().asFile.writeText(source.get().asFile.readText() + version.get()) } } ``` Or imperatively via `inputs.file(...)`, `inputs.property(...)`, `outputs.dir(...)`. ## The up-to-date decision Before running, Gradle: 1. Builds a **fingerprint** (hashes) of every input file's content + every `@Input` value. 2. Loads the previous task **execution history** from `.gradle`. 3. Marks the task `UP-TO-DATE` iff: - input fingerprints match the previous run, **and** - output fingerprints match (outputs untouched since produced), **and** - the declared outputs still **exist**, **and** - the task's implementation/action classpath is unchanged. If any check fails, the action re-runs. ## Things that defeat up-to-date checking - **No declared outputs** → always runs (Gradle can't prove the result is current). - **Undeclared inputs** → stale results / false UP-TO-DATE; a correctness bug. - `outputs.upToDateWhen { false }` → force always-run. - `--rerun-tasks` (CLI) → ignore history for this invocation. - Changing the task class or a `doLast` lambda → implementation change → re-run. ## Related outcomes - `FROM-CACHE` — output restored from the build cache (a superset feature; needs `@CacheableTask`). - `NO-SOURCE` — inputs declared but empty, so there is nothing to do. Up-to-date checking is **per-task** and happens during Execution, immediately before each task's actions would run.
- Why does a task with no declared outputs never show UP-TO-DATE?Gradle has no way to verify the produced result is still current, so it conservatively re-executes the task every time.
- What's the danger of an undeclared input?Gradle may report UP-TO-DATE even though the real input changed, producing a stale/incorrect build — a correctness bug, not just a performance one.
- How do UP-TO-DATE and FROM-CACHE relate?UP-TO-DATE skips because the local outputs are still valid; FROM-CACHE restores outputs from the (possibly remote) build cache when a matching cache key exists.
Like a build stamp on a part: if the part and its spec are unchanged since the last stamp, you reuse it instead of remanufacturing.
saying these in an interview costs you the question
- Saying Gradle compares file timestamps only — it fingerprints content/hashes.
- Claiming a task with declared inputs but no outputs can be UP-TO-DATE.
- Ignoring that deleting an output forces a re-run.