skip to content

Declaring Inputs & Outputs

Using task.inputs and task.outputs on ad-hoc tasks, and what those declarations buy you: incrementality and inferred dependencies. Interviewers ask because a missing output declaration is the classic reason a task never reports UP-TO-DATE.

on this pageshow

questions

5

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