When and how would you use @OutputFiles or @OutputDirectories (the map-accepting forms) instead of single @OutputFile/@OutputDirectory properties?
answer
- plural forms accept FileCollection or Map<String,File>
- map keys = stable per-output identity
- precise up-to-date per key
- helps overlapping-output handling
- use when output count is dynamic
basics
~10 sUse @OutputFiles/@OutputDirectories when a task produces multiple, dynamically-keyed outputs. Backing the property with a Map<String, File> gives each output a stable identity key, which Gradle uses for reliable up-to-date checks and overlapping-output handling.
solid answer
~40 s`@OutputFile`/`@OutputDirectory` declare exactly **one** output path. When a task emits a **variable set** of outputs — e.g. one report per input module, known only at execution time — use the plural `@OutputFiles`/`@OutputDirectories`. These accept a `FileCollection` **or** a `Map<String, File>`. The map form is preferred because the **keys give each output a stable identity** across builds: Gradle associates a fingerprint with each key, so adding/removing/renaming one output is tracked precisely rather than as an opaque collection diff. This matters for correct incremental up-to-date checks and for Gradle's *overlapping outputs* handling (so two tasks writing into a shared directory don't confuse each other). Prefer a single keyed property over many ad-hoc `@OutputFile` fields when the count is dynamic.
code
kotlin · 13 linesabstract class SplitArchive : DefaultTask() {
@get:Input abstract val parts: SetProperty<String>
@get:OutputDirectory abstract val outDir: DirectoryProperty
@get:OutputFiles
val outputs: Map<String, File>
get() = parts.get().associateWith { name ->
outDir.file("$name.bin").get().asFile
}
@TaskAction
fun run() = outputs.forEach { (_, f) -> f.writeBytes(byteArrayOf()) }
}go deeper
Know the plural annotations exist for tasks producing more than one output.
Explain the Map<String,File> form and that keys give per-output identity for up-to-date checks.
Discuss overlapping-output handling and stable-key fingerprinting, and when fixed @OutputFile fields are clearer.
Set conventions for plugin authors on modeling dynamic output sets so large multi-output tasks stay correctly incremental and cache-safe.
## Single vs. plural output annotations Gradle's output annotations come in singular and plural forms: - `@OutputFile` / `@OutputDirectory` — exactly one path, typically a `RegularFileProperty` / `DirectoryProperty`. - `@OutputFiles` / `@OutputDirectories` — a **collection** of paths, typically a `FileCollection`, or a **`Map<String, File>`**. Use the plural forms when the **number of outputs isn't fixed** at authoring time — you discover them at execution (one artifact per discovered input, per locale, per target platform, etc.). ## Why the Map form is special A `FileCollection` is an unordered bag of files. If a task's output set changes, Gradle can tell *something* changed but the individual outputs have no durable identity. A `Map<String, File>` gives each output a **stable key**: ``` @get:OutputFiles val reports: Map<String, File> get() = targets.get().associateWith { layout.buildDirectory.file("reports/$it.html").get().asFile } ``` Gradle keeps a per-key fingerprint. Concretely this helps with: 1. **Precise up-to-date checks** — if only the `"linux"` output is deleted/modified, Gradle knows which output is stale rather than invalidating the whole collection opaquely. 2. **Overlapping outputs** — when multiple tasks write into the same directory tree, Gradle uses output ownership/identity to decide what each task is responsible for; stable keys make that reliable. 3. **Stable association across runs** — the key is logical (e.g. a target name), so even if the absolute file path shifts, the identity is preserved. ## How to wire it Modern style backs the values with the **Provider/Property** API so paths are resolved lazily and participate in task-input/output wiring: ``` abstract class RenderReports : DefaultTask() { @get:Input abstract val targets: SetProperty<String> @get:OutputDirectory abstract val outDir: DirectoryProperty @get:OutputFiles val files: Map<String, File> get() = targets.get().associateWith { outDir.file("$it.html").get().asFile } } ``` ## When NOT to use them If you genuinely have a small, **fixed** set of named outputs, just declare several `@OutputFile` properties — it's clearer. The plural map form earns its keep only when the set is **dynamic**.
- Why is Map<String, File> often preferred over FileCollection for @OutputFiles?The map keys give each output a stable logical identity, so Gradle fingerprints outputs per key — enabling precise up-to-date detection and reliable overlapping-output handling instead of treating the set as an opaque bag.
- When should you just use multiple @OutputFile fields instead?When the set of outputs is small and fixed at authoring time; the plural map form is justified only when the number of outputs is dynamic/data-driven.
saying these in an interview costs you the question
- Claiming @OutputFiles only accepts a FileCollection — it also accepts Map<String, File>.
- Saying the keys are cosmetic; they carry real identity used for fingerprinting and overlap resolution.
- Using a getter that does heavy work at configuration time instead of lazily resolving paths.