skip to content

Inputs, Outputs & Up-to-Date

Declaring what a task reads and writes, and how those declarations let Gradle skip work entirely. Interviewers ask because a task with undeclared inputs or outputs quietly defeats incremental builds and caching.

on this pageshow

explore

questions

20

Why would you declare inputs and outputs on an ad-hoc Gradle task using task.inputs and task.outputs?

level: juniorimportance: must knowfreq 70%

answer

  1. no inputs/outputs => always runs
  2. fingerprint compared run-to-run
  3. UP-TO-DATE skip
  4. feeds build cache key
  5. enables dependency inference

basics

~10 s

Declaring inputs/outputs lets Gradle skip the task when nothing changed (up-to-date checking) and reuse cached results. Without them Gradle has no fingerprint, so the task always runs.

solid answer

~30 s

Inputs and outputs are how Gradle knows whether a task actually needs to run. On an ad-hoc task you call `inputs.file(...)`, `inputs.dir(...)`, `inputs.property(...)` and `outputs.file(...)`/`outputs.dir(...)` inside the configuration. Before executing, Gradle fingerprints the declared inputs and outputs; if nothing changed since the last run it marks the task `UP-TO-DATE` and skips it. Declared inputs/outputs also feed the build cache (a matching fingerprint can be restored from cache) and let Gradle infer task ordering: if task B's input is task A's output, A runs first automatically. A task with no declared inputs/outputs always runs, because Gradle has nothing to compare.

code

kotlin · 6 lines
kotlin
tasks.register("generateReport") {
    inputs.file("src/data.csv")
    inputs.property("title", "Q3")
    outputs.file(layout.buildDirectory.file("report.txt"))
    doLast { /* produce report.txt from data.csv */ }
}

go deeper

for a junior

Know that inputs/outputs let Gradle skip unchanged tasks and that a task without them always runs.

for a middle

Explain fingerprinting, UP-TO-DATE vs FROM-CACHE, and the inputs.file/dir/property and outputs.file/dir methods on ad-hoc tasks.

for a senior

Connect inputs/outputs to dependency inference, the build cache key, and the cost of unannotated tasks in large builds.

for a principal

Frame declared inputs/outputs as the contract that makes a build correctly incremental and cacheable org-wide, and the maintenance discipline that requires.

## What inputs and outputs are Every Gradle task can declare what data it reads (its **inputs**) and what it produces (its **outputs**). For an *ad-hoc* task — one you write directly in the build script with `tasks.register("name") { ... }` rather than a custom task class — you declare these at configuration time through two runtime objects on the task: - `inputs` (a `TaskInputs`): `inputs.file(...)`, `inputs.files(...)`, `inputs.dir(...)`, `inputs.property(name, value)`. - `outputs` (a `TaskOutputs`): `outputs.file(...)`, `outputs.files(...)`, `outputs.dir(...)`. ## Why it matters: incremental builds Gradle is an **incremental** build tool. Before running a task it computes a fingerprint (hash) of every declared input — file contents/paths for files, the serialized value for properties — plus a snapshot of the declared outputs. It stores this in the build's task history. On the next run it recomputes the fingerprint and compares: - If inputs **and** outputs are unchanged, the task is reported `UP-TO-DATE` and its actions are skipped entirely. - If any input changed, an output was deleted/modified externally, or the task has no declared inputs/outputs at all, the task executes. A task that declares **nothing** is always out of date, so it runs every build — correct but slow. ## Why it matters: dependency inference When you wire one task's output into another task's input (for example by passing a producer task or its output property), Gradle infers an implicit `dependsOn`: the producer runs before the consumer, and you don't write an explicit dependency. This is the modern, provider-based way to connect tasks. ## Why it matters: the build cache Declared inputs form the basis of the **build cache key**. With the build cache enabled, a task whose input fingerprint matches a previously-seen one can have its outputs *restored from cache* instead of executing — even on a different machine. ```kotlin tasks.register("generateReport") { inputs.file("src/data.csv") inputs.property("title", project.findProperty("reportTitle") ?: "Report") outputs.file(layout.buildDirectory.file("report.txt")) doLast { // read data.csv, write report.txt } } ``` If `data.csv` and the `title` property are unchanged and `report.txt` still exists, the second invocation is `UP-TO-DATE`.

  • What happens if you declare an output file but the task never creates it?
    Gradle considers the output missing on the next up-to-date check, so the task is re-run. A task that claims an output but doesn't produce it will never be UP-TO-DATE and breaks downstream consumers that depend on that file.
  • Does declaring inputs/outputs require writing a custom task class?
    No. The inputs/outputs runtime API works on any task, including ad-hoc tasks configured inline. Custom task types additionally support annotation-based declarations like @InputFile/@OutputFile, but the runtime API is available everywhere.

Like a Makefile timestamp rule: declare the source and the target, and the build only rebuilds the target when the source is newer — except Gradle hashes content, not just timestamps.

saying these in an interview costs you the question

  • Claiming Gradle compares only file timestamps — it fingerprints content/paths, not just mtime.
  • Saying a task with no declared inputs/outputs is cached or skipped — it always runs.

context

open as a page

What does the --rerun-tasks command-line flag do, and when would you reach for it?

level: juniorimportance: must knowfreq 60%

basics

~10 s

--rerun-tasks tells Gradle to ignore up-to-date checks for the whole build and re-execute every task in the requested task graph, even tasks it considers UP-TO-DATE. Useful to force a clean re-run while debugging.

open as a page

When you run a Gradle build and see a task labeled UP-TO-DATE in the output, what does that tell you and why did it happen?

level: juniorimportance: must knowfreq 78%

basics

~10 s

UP-TO-DATE means Gradle skipped running the task because its inputs and outputs haven't changed since the last run, so re-executing would produce identical results.

open as a page

How do you make a Gradle task always run (never be considered up-to-date), and when would you do that?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Add outputs.upToDateWhen { false } to the task. The predicate always returns false, so Gradle never treats the task as up-to-date and runs it every build. Useful for tasks with side effects Gradle can't track.

open as a page

What is the difference between inputs.property(...) and inputs.file(...) on an ad-hoc task, and when do you use each?

level: middleimportance: must knowfreq 55%

basics

~10 s

inputs.file declares a file/path whose contents are fingerprinted. inputs.property declares a non-file value (string, number, flag) compared by its serialized value. Use file for file inputs, property for config values.

open as a page

Walk through what Gradle does with declared inputs/outputs to decide whether an ad-hoc task is UP-TO-DATE.

level: middleimportance: must knowfreq 60%

basics

~10 s

Gradle fingerprints the declared inputs and snapshots outputs, stores them in task history, and on the next run recomputes and compares. If both match the prior run, it skips the task as UP-TO-DATE.

open as a page

How does the --rerun flag differ from --rerun-tasks, and how do you target a single task with it?

level: middleimportance: must knowfreq 50%

basics

~10 s

--rerun (built-in task option) forces a single, specific task to ignore its up-to-date/cache state, written as --rerun after the task name. --rerun-tasks forces every task in the whole build to re-execute.

open as a page

A teammate complains that a task keeps re-running even though 'nothing changed.' From a consumer's seat, how do you find out why Gradle decided the task was no longer up-to-date?

level: middleimportance: must knowfreq 68%

basics

~10 s

Run the build with --info. For each non-skipped task Gradle prints a reason like 'Input property X has changed' or 'Output file Y has been removed', telling you exactly why it re-executed.

open as a page

Gradle annotates tasks in the console with outcomes like UP-TO-DATE, FROM-CACHE, NO-SOURCE, and SKIPPED. Walk me through what each of these means.

level: middleimportance: must knowfreq 70%

basics

~10 s

UP-TO-DATE = skipped, outputs already current. FROM-CACHE = outputs fetched from the build cache. NO-SOURCE = no input source files to process. SKIPPED = excluded or its onlyIf condition was false.

open as a page

What is the difference between `outputs.cacheIf {}` and `outputs.upToDateWhen {}`?

level: middleimportance: must knowfreq 55%

basics

~10 s

upToDateWhen 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.

open as a page

After a build, Gradle prints a line like '9 actionable tasks: 2 executed, 5 up-to-date, 2 from cache'. How do you read this summary, and what would it tell you about your build's incrementality?

level: juniorimportance: should knowfreq 50%

basics

~10 s

It counts actionable tasks by outcome: how many actually ran (executed) versus were skipped as up-to-date or restored from cache. Mostly up-to-date/from-cache means a well-behaving incremental build; many executed means real work was redone.

open as a page

How does declaring a task's outputs as another task's inputs let Gradle infer task dependencies without an explicit dependsOn?

level: middleimportance: should knowfreq 50%

basics

~10 s

If you wire a producer task's output (or its output Provider) into a consumer task's input, Gradle sees the producer creates that file and automatically runs it first — no explicit dependsOn needed.

open as a page

What does outputs.upToDateWhen { false } do, and what are the trade-offs of putting it in a build script?

level: middleimportance: should knowfreq 40%

basics

~10 s

It makes a task's up-to-date check always fail, so the task re-executes on every build instead of being skipped as UP-TO-DATE. It's a programmatic, permanent way to force rerun (unlike the one-shot CLI flags).

open as a page

Show how to write a conditional `outputs.upToDateWhen` predicate (not just `false`), and explain when the predicate is evaluated.

level: middleimportance: should knowfreq 38%

basics

~10 s

outputs.upToDateWhen { task -> someCondition } returns a boolean from a Spec<Task>. It's evaluated at execution time, during the up-to-date check, before the task's actions run.

open as a page

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%

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.

open as a page

What problems arise when you resolve input/output files eagerly at configuration time instead of using lazy providers, and how do you avoid them?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Resolving files eagerly (.get(), new File(...)) at configuration time loses producer links (breaking dependency inference), does work even when the task won't run, and breaks the configuration cache. Use Providers and layout.buildDirectory instead.

open as a page

What is @DisableCachingByDefault and how does disabling caching relate to forcing a task to rerun?

level: seniorimportance: should knowfreq 30%

basics

~20 s

@DisableCachingByDefault marks a task type as not cached by the build cache by default, so its outputs are never restored FROM-CACHE. That removes one path that could skip real work, but it does NOT disable up-to-date checking.

open as a page

A teammate adds --rerun-tasks to the CI build because a task 'keeps producing stale output'. How would you diagnose and respond?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Forcing reruns in CI hides the real bug and kills incremental/cache speed. Find why the task skips when it shouldn't — usually missing or wrong input/output declarations — and fix the declaration so up-to-date checking is correct.

open as a page

On the same project, the same task can show UP-TO-DATE on one run and FROM-CACHE on another. Explain the difference and when each occurs.

level: seniorimportance: should knowfreq 55%

basics

~20 s

UP-TO-DATE: the correct outputs are already on disk locally and inputs are unchanged, so nothing happens. FROM-CACHE: the outputs are missing locally, so Gradle restores a matching entry from the build cache instead of running the task.

open as a page

A teammate added `outputs.upToDateWhen { false }` to a deploy task and the build is now slow and reruns it constantly even in CI cache scenarios. How do you reason about whether this is correct, and what alternatives exist?

level: seniorimportance: should knowfreq 30%

basics

~20 s

For a genuine deploy with untrackable remote side effects, upToDateWhen { false } is correct — deploys should always run. The fix isn't to remove it but to ensure the task is non-cacheable and isn't wired as a dependency of fast inner-loop tasks.

open as a page