What is an inferred (implicit) task dependency, and how does wiring a producer's output Provider into a consumer's input property make explicit `dependsOn` unnecessary?
answer
- output Provider carries its producer task
- wire output -> input = auto dependency
- flatMap keeps it lazy
- no manual dependsOn needed
- same graph as explicit edges
basics
~20 sIf you connect a producing task's output Provider (e.g. its RegularFileProperty) directly to a consuming task's input property, Gradle sees the output 'carries' its producing task and automatically adds the dependency — no manual dependsOn needed.
solid answer
~40 sModern Gradle tracks **task dependencies through the file-property graph**. A task's `@OutputFile`/`@OutputDirectory` is a `Provider` (e.g. `RegularFileProperty`) that internally knows the task that produces it. When you assign that Provider into another task's `@InputFile` property — e.g. `consumer.inputFile.set(producer.flatMap { it.outputFile })` — the input property *carries* the producer task as an implicit dependency. So when Gradle resolves the consumer's inputs to build the graph, it discovers the producer and schedules it first **automatically**. This is the idiomatic, lazy approach: it works with `register` (configuration avoidance), survives task reordering, and keeps dependencies in sync with the actual data flow. You avoid both the boilerplate and the bugs of hand-written `dependsOn` (forgetting an edge, or a stale edge after refactor). Manual `dependsOn` is reserved for cases with no file-property data flow to wire.
code
kotlin · 7 linesval generate = tasks.register<GenerateTask>("generate") {
outputFile.set(layout.buildDirectory.file("gen/out.txt"))
}
val consume = tasks.register<ConsumeTask>("consume") {
// No dependsOn(generate) — wiring the Provider infers it.
inputFile.set(generate.flatMap { it.outputFile })
}go deeper
Recognize that connecting an output to an input can make Gradle run the producer first automatically.
Explain that output Providers carry their producing task and that flatMap keeps wiring lazy; show the producer/consumer snippet.
Argue why inferred dependencies are more robust than manual dependsOn (data-flow-derived, refactor-safe, ties into incrementality/caching).
Position provider-wiring as the standard for a large build to eliminate hand-maintained edges and guarantee correct graphs at scale.
## The core idea Gradle's lazy `Provider`/`Property` API does more than defer computation — output file properties are **task-aware**. A `RegularFileProperty` produced by a task records that task as its producer. When such a Provider flows into another task's input, Gradle treats the producing task as an *inferred dependency* of the consumer. ## Producer task ```kotlin abstract class GenerateTask : DefaultTask() { @get:OutputFile abstract val outputFile: RegularFileProperty @TaskAction fun run() = outputFile.get().asFile.writeText("generated") } val generate = tasks.register<GenerateTask>("generate") { outputFile.set(layout.buildDirectory.file("gen/out.txt")) } ``` ## Consumer task — no dependsOn ```kotlin abstract class ConsumeTask : DefaultTask() { @get:InputFile abstract val inputFile: RegularFileProperty @TaskAction fun run() = println(inputFile.get().asFile.readText()) } val consume = tasks.register<ConsumeTask>("consume") { // Wire producer output -> consumer input. flatMap keeps it lazy. inputFile.set(generate.flatMap { it.outputFile }) } ``` Running `./gradlew consume` runs `generate` first — **without any `dependsOn`**. The `inputFile` property carries `generate` as its producer. ## Why this beats manual dependsOn - **Correctness:** the dependency is derived from the actual data flow, so it can't drift out of sync when you rename/move tasks. - **Laziness:** works with configuration avoidance (`register`, `flatMap`, `map`); the producer is only configured if actually needed. - **Up-to-date checking:** the same wiring makes the file an `@InputFile`, so the consumer participates correctly in incremental builds and caching. ## How Gradle implements it Internally, output properties expose a `TaskDependency` (their `producer`). During graph construction Gradle calls `Task.getTaskDependencies()` and the resolution of each input `Provider` contributes the producer task. This is the same `TaskDependencyContainer` mechanism that `dependsOn` feeds into — inferred edges and explicit edges end up in the same graph. ## When you still need dependsOn If there is no file/property data flow (e.g. one task must run after another purely for side effects with no shared artifact), there's nothing to wire, so an explicit `dependsOn` (or an ordering hook) is the correct tool.
- Why use `flatMap` instead of `generate.get().outputFile`?`flatMap` keeps the wiring lazy and avoids eagerly realizing the `generate` task at configuration time, preserving configuration avoidance; `get()` would force it.
- Does the inferred dependency also help up-to-date checking?Yes — the same wiring registers the file as an `@InputFile`, so the consumer's up-to-date and cache key correctly reflect the producer's output.
saying these in an interview costs you the question
- Saying you 'must always add dependsOn' even when output→input is wired — that's redundant.
- Calling `.get()` on the producer at configuration time, breaking laziness and inference.