What is the difference between inputs.property(...) and inputs.file(...) on an ad-hoc task, and when do you use each?
answer
- file => content hash
- property => serialized value compared
- undeclared config value = stale output bug
- pass Provider for lazy values
- optional(true) for maybe-absent files
basics
~10 sinputs.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.
solid answer
~40 s`inputs.file(path)` (and `inputs.files`, `inputs.dir`) tell Gradle that a file or directory is an input; Gradle fingerprints the *content and paths* of those files and considers the task out of date if they change. `inputs.property(name, value)` declares a scalar/config value — a string, number, boolean, enum — that affects the output but isn't a file; Gradle compares its serialized form between runs. You use `inputs.property` for things like a version string, a flag, or a target environment that should invalidate the task when it changes. A common bug is hardcoding such a value inside `doLast` without declaring it: changing it won't invalidate the task, so the output goes stale. Property values must be serializable (and, for the configuration cache, comparable); if a property is expensive or lazy, pass a `Provider` so it's resolved correctly.
code
kotlin · 6 linestasks.register("render") {
inputs.file("template.txt")
inputs.property("env", providers.gradleProperty("env").orElse("dev"))
outputs.file(layout.buildDirectory.file("out.txt"))
doLast { /* render using env */ }
}go deeper
Know file inputs track file contents and property inputs track plain values like a version string.
Explain when to use each, the staleness bug from undeclared config values, optional(true), and serializability.
Discuss Provider-based properties, configuration-cache compatibility, and avoiding non-deterministic property values.
Set conventions so teams declare all output-affecting values, keeping builds reproducible and cacheable across the org.
## Two kinds of inputs Gradle distinguishes **file inputs** from **value (property) inputs** because it fingerprints them differently. ### File inputs — `inputs.file` / `inputs.files` / `inputs.dir` These register filesystem locations. Gradle snapshots them by **content hash and relative path** (the exact normalization depends on the input type). If a byte changes, a file is added/removed, or (depending on path sensitivity) a path moves, the fingerprint changes and the task re-runs. Accepted arguments are anything resolvable to files: `String` paths, `File`, `RegularFile`/`Directory`, a `FileCollection`, or a `Provider` of those. ### Value inputs — `inputs.property` `inputs.property(name, value)` registers a **non-file value** keyed by `name`. Gradle keeps the serialized value in the task history and compares it run-to-run. Use it for anything that changes the output but isn't itself a file: - a version or build number, - a feature flag or boolean toggle, - a target environment / profile name, - a numeric parameter. The value should be serializable and stable across runs (avoid capturing `System.currentTimeMillis()` unless you *want* perpetual reruns). For lazily-computed values, pass a `Provider` — Gradle resolves it at the right time and tracks it for the configuration cache. ## The classic staleness bug ```kotlin val env = project.findProperty("env") ?: "dev" tasks.register("render") { inputs.file("template.txt") // BUG if 'env' is used in doLast but NOT declared: inputs.property("env", env) // <-- this line is the fix outputs.file(layout.buildDirectory.file("out.txt")) doLast { /* uses env to render template.txt */ } } ``` Without the `inputs.property("env", env)` line, switching `-Penv=prod` leaves the task `UP-TO-DATE` and you ship the wrong output. Declaring it makes the env value part of the fingerprint. ## Optional inputs and validation `inputs.file(...).optional(true)` marks a file input as allowed-to-be-absent. By default Gradle validates that declared input files exist before running. ## Summary table | API | Tracks | Use for | |---|---|---| | `inputs.file/files/dir` | file content + paths | source files, config files, directories | | `inputs.property` | serialized value | versions, flags, env names, numbers |
- What happens if you pass System.currentTimeMillis() to inputs.property?The property value changes on every configuration, so the task is never UP-TO-DATE and re-runs every build. That defeats incrementality; only declare values that should legitimately invalidate the task.
- How do you tell Gradle an input file is allowed to be missing?Chain .optional(true) on the input declaration, e.g. inputs.file("maybe.txt").optional(true). Otherwise Gradle's input validation fails the build when the declared file doesn't exist.
- Why prefer a Provider over a resolved value for inputs.property?A Provider defers computation and is tracked correctly by the configuration cache, avoiding eager work at configuration time and capturing the value when it's actually needed.
saying these in an interview costs you the question
- Using inputs.file for a plain string/version value (or vice versa).
- Reading a project property inside doLast without declaring it as an input — silent staleness.
- Passing a constantly-changing value (timestamp, random) to inputs.property and wondering why nothing is ever up to date.