skip to content

How does Gradle decide a task is UP-TO-DATE during execution, and what makes it re-run?

level: middleimportance: must knowfreq 60%

answer

  1. snapshot inputs + outputs
  2. match previous fingerprint => skip
  3. outputs must still exist
  4. no outputs => always runs
  5. --rerun-tasks / upToDateWhen{false}

basics

~20 s

Before 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 s

Gradle'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 lines
kotlin
tasks.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

for a junior

Say Gradle skips a task when its inputs and outputs are unchanged, showing UP-TO-DATE.

for a middle

Explain input/output declarations, fingerprinting, the existence-of-outputs check, and what forces re-run.

for a senior

Discuss correctness implications of undeclared inputs, implementation-change detection, and the relationship to the build cache and NO-SOURCE.

for a principal

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.

context