What is the purpose of annotating task properties with @Input, @InputFile, and @OutputFile in a custom Gradle task?
answer
- inputs + outputs = up-to-date fingerprint
- @Input = scalar value
- @InputFile = file content hashed
- unannotated = ignored
- skip when nothing changed
basics
~10 sThey tell Gradle which properties are a task's inputs and outputs so Gradle can decide if the task is up-to-date and skip running it when nothing relevant changed.
solid answer
~30 sAnnotations like `@Input`, `@InputFile`, and `@OutputFile` declare a task's input/output 'fingerprint'. Gradle snapshots these before and after a run; if all inputs and outputs are unchanged since the last build, the task is marked `UP-TO-DATE` and skipped. `@Input` is for simple values (strings, numbers, enums); `@InputFile`/`@InputFiles`/`@InputDirectory` track file content (hashed), not just the path; `@OutputFile`/`@OutputDirectory` track produced artifacts. Properties without any annotation are ignored for up-to-date checks (and ArchUnit-style validation will warn). Declaring them correctly is what makes incremental builds and caching work — miss an input and the task wrongly stays up-to-date when it should rerun.
code
kotlin · 10 linesabstract class GreetingTask : DefaultTask() {
@get:Input
abstract val message: Property<String>
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun run() = outputFile.get().asFile.writeText(message.get())
}go deeper
Know that inputs/outputs annotations let Gradle skip unchanged tasks (UP-TO-DATE) and name @Input vs @InputFile vs @OutputFile.
Explain content hashing vs value snapshotting and the stale-output bug from forgetting an annotation.
Discuss over-/under-declaration trade-offs and how correct declarations are the precondition for the build cache and incremental builds.
Frame input/output correctness as a build-reliability and reproducibility concern across a multi-module org; enforce via validation plugins in CI.
## Why annotations exist Gradle is an *incremental* build tool. To avoid redoing work, it must know exactly what each task consumes (inputs) and what it produces (outputs). It learns this from annotations on the task class's properties (getters). ## Up-to-date checking in one sentence Before running a task, Gradle computes a fingerprint of every declared input and output. After the task runs, it stores that fingerprint. On the next build it recomputes; if nothing changed, the task is `UP-TO-DATE` and skipped. ## The core annotations - `@Input` — a simple scalar value: `String`, numbers, `Boolean`, enums, or `Serializable` objects. The *value* is part of the fingerprint. - `@InputFile` — a single file whose **content** matters; Gradle hashes the bytes, so the same content under a different path is still 'same input' (depending on normalization). - `@InputFiles` — a `FileCollection`/iterable of files. - `@InputDirectory` — a directory tree, hashed recursively. - `@OutputFile` / `@OutputDirectory` — declares produced artifacts; Gradle also stamps them so it can detect if outputs were tampered with. ## What happens if you forget A property that influences behavior but isn't annotated is invisible to Gradle. The task stays `UP-TO-DATE` even though the relevant value changed — a stale-output bug. Conversely, over-declaring inputs (e.g., a timestamp) makes the task *never* up-to-date. ## Code shape ```kotlin abstract class GreetingTask : DefaultTask() { @get:Input abstract val message: Property<String> @get:InputFile abstract val template: RegularFileProperty @get:OutputFile abstract val outputFile: RegularFileProperty @TaskAction fun run() { outputFile.get().asFile.writeText( template.get().asFile.readText() + message.get() ) } } ``` Gradle now reruns this task only when `message`, the `template` file content, or the `outputFile` changes.
- What happens at build time if a property that affects output is left unannotated?Gradle never sees it as an input, so the task can stay UP-TO-DATE even when that value changes, producing stale outputs. Gradle's validation may also emit a warning about an untracked property.
- Why is @InputFile based on content rather than the file path?Gradle hashes the file's bytes, so two builds with identical content (even moved/renamed, subject to normalization) are treated as the same input — this makes up-to-date and cache checks portable across machines and checkout locations.
Like a recipe card listing ingredients (inputs) and the finished dish (output): if the same ingredients are already in the fridge, you don't cook again.
saying these in an interview costs you the question
- Saying annotations are just documentation — they actively drive up-to-date and cache decisions.
- Claiming @Input is for files — @Input is for scalar values; files use @InputFile/@InputFiles.