Why would you declare inputs and outputs on an ad-hoc Gradle task using task.inputs and task.outputs?
answer
- no inputs/outputs => always runs
- fingerprint compared run-to-run
- UP-TO-DATE skip
- feeds build cache key
- enables dependency inference
basics
~10 sDeclaring 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 sInputs 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 linestasks.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
Know that inputs/outputs let Gradle skip unchanged tasks and that a task without them always runs.
Explain fingerprinting, UP-TO-DATE vs FROM-CACHE, and the inputs.file/dir/property and outputs.file/dir methods on ad-hoc tasks.
Connect inputs/outputs to dependency inference, the build cache key, and the cost of unannotated tasks in large builds.
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.