skip to content

When and how would you use @OutputFiles or @OutputDirectories (the map-accepting forms) instead of single @OutputFile/@OutputDirectory properties?

level: middleimportance: should knowfreq 28%

answer

  1. plural forms accept FileCollection or Map<String,File>
  2. map keys = stable per-output identity
  3. precise up-to-date per key
  4. helps overlapping-output handling
  5. use when output count is dynamic

basics

~10 s

Use @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 lines
kotlin
abstract 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

for a junior

Know the plural annotations exist for tasks producing more than one output.

for a middle

Explain the Map<String,File> form and that keys give per-output identity for up-to-date checks.

for a senior

Discuss overlapping-output handling and stable-key fingerprinting, and when fixed @OutputFile fields are clearer.

for a principal

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.

context