// Copyright (C) 2025 Gradle, Inc.
//
// Licensed under the Creative Commons Attribution-Noncommercial-ShareAlike 4.0 International License.;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

[[best_practices_for_tasks]]
= Best Practices for Tasks

[[avoid_depends_on]]
== Avoid DependsOn

The task link:{javadocPath}/org/gradle/api/DefaultTask.html#setDependsOn(java.lang.Iterable)[dependsOn] method should only be used for <<organizing_tasks.adoc#sec:lifecycle_tasks,lifecycle tasks>> (tasks without task actions).

=== Explanation

Tasks with actions should declare their inputs and outputs so that Gradle’s up-to-date checking can automatically determine when these tasks need to be run or rerun.

Using `dependsOn` to link tasks is a much coarser-grained mechanism that does **not** allow Gradle to understand why a task requires a prerequisite task to run, or which specific files from a prerequisite task are needed.
`dependsOn` forces Gradle to assume that _every_ file produced by a prerequisite task is needed by this task.
This can lead to unnecessary task execution and decreased build performance.

=== Example

Here is a task that writes output to two separate files:

====
include::sample[dir="snippets/best-practices/avoidDependsOn-do/kotlin",files="build.gradle.kts[tags=depended-upon-task-setup]"]
include::sample[dir="snippets/best-practices/avoidDependsOn-do/groovy",files="build.gradle[tags=depended-upon-task-setup]"]
====

<1> *Task With Multiple Outputs*: `helloWorld` task prints "Hello" to its `messageFile` and "World" to its `audienceFile`.
<2> *Registering the Task*: `helloWorld` produces "message.txt" and "audience.txt" outputs.

==== Don't Do This

If you want to translate the greeting in the `message.txt` file using another task, you could do this:

====
include::sample[dir="snippets/best-practices/avoidDependsOn-avoid/kotlin",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/avoidDependsOn-avoid/groovy",files="build.gradle[tags=avoid-this]"]
====

<1> *Translation Task Setup*: `translateBad` requires `helloWorld` to run first to produce the message file otherwise it will fail with an error as the file does not exist.
<2> *Explicit Task Dependency*: Running `translateBad` will cause `helloWorld` to run first, but Gradle does not understand _why_.

==== Do This Instead

Instead, you should explicitly wire task inputs and outputs like this:

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidDependsOn-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidDependsOn-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

====
include::sample[dir="snippets/best-practices/avoidDependsOn-do/kotlin",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/avoidDependsOn-do/groovy",files="build.gradle[tags=do-this]"]
====

<1> *Register Implicit Task Dependency*: `translateGood` requires only one of the files that is produced by `helloWorld`.

Gradle now understands that `translateGood` requires `helloWorld` to have run successfully first because it needs to create the `message.txt` file which is then used by the translation task.
Gradle can use this information to optimize task scheduling.
Using the `map` method avoids eagerly retrieving the `helloWorld` task until the output is needed to determine if `translateGood` should run.

=== References

- <<incremental_build.adoc#sec:task_input_output_side_effects,Task Inputs and Outputs>>

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:inputs-and-outputs,#inputs-and-outputs>>`, `<<tags_reference.adoc#tag:up-to-date-checking,#up-to-date-checking>>`

[[use_cacheability_annotations]]
== Favor `@CacheableTask` and `@DisableCachingByDefault` over `cacheIf(Spec)` and `doNotCacheIf(String, Spec)`

The link:{javadocPath}/org/gradle/api/tasks/TaskOutputs.html#cacheIf(org.gradle.api.specs.Spec)[`cacheIf`] and link:{javadocPath}/org/gradle/api/tasks/TaskOutputs.html#doNotCacheIf(java.lang.String,org.gradle.api.specs.Spec)[`doNotCacheIf`] methods should only be used in situations where the <<build_cache.adoc#build_cache,cacheability>> of a task varies between different task instances or cannot be determined until the task is executed by Gradle.
You should instead favor annotating the task class itself with link:{javadocPath}/org/gradle/api/tasks/CacheableTask.html[`@CacheableTask`] annotation for any task that is _always_ cacheable.
Likewise, the link:{javadocPath}/org/gradle/work/DisableCachingByDefault.html[`@DisableCachingByDefault`] should be used to always disable caching for all instances of a task type.

=== Explanation

Annotating a task type will ensure that _each task instance_ of that type is properly understood by Gradle to be cacheable (or not cacheable).
This removes the need to remember to configure each of the task instances separately in build scripts.

Using the annotations also _documents_ the intended cacheability of the task type within its own source, appearing in Javadoc and making the task's behavior clear to other developers without requiring them to inspect each task instance's configuration.
It is also slightly more efficient than running a test to determine cacheability.

Remember that only tasks that produce reproducible and relocatable output should be marked as `@CacheableTask`.

=== Example

==== Don't Do This

If you want to reuse the output of a task, you shouldn't do this:

====
include::sample[dir="snippets/best-practices/useCacheabilityAnnotations-avoid/kotlin",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/useCacheabilityAnnotations-avoid/groovy",files="build.gradle[tags=avoid-this]"]
====

<1> *Define a Task*: The `BadCalculatorTask` type is deterministic and produces relocatable output, but is not annotated.
<2> *Mark the Task Instance as Cacheable*: This example shows how to mark a specific task instance as cacheable.
<3> *Forget to Mark a Task Instance as Cacheable*: Unfortunately, the `addBad2` instance of the `BadCalculatorTask` type is not marked as cacheable, so it will not be cached, despite behaving the same as `addBad1`.

==== Do This Instead

As this task meets the criteria for cacheability (we can imagine a more complex calculation in the `@TaskAction` that would benefit from automatic work avoidance via caching), you should mark the _task type itself_ as cacheable like this:

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/useCacheabilityAnnotations-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/useCacheabilityAnnotations-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

====
include::sample[dir="snippets/best-practices/useCacheabilityAnnotations-do/kotlin",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/useCacheabilityAnnotations-do/groovy",files="build.gradle[tags=do-this]"]
====

<1> *Annotate the Task Type*: Applying the `@CacheableTask` to a task type informs Gradle that instances of this task should _always_ be cached.
<2> *Nothing Else Needs To Be Done*: When we register task instances, nothing else needs to be done - Gradle knows to cache them.

=== References

- <<more_about_tasks.adoc#sec:caching_tasks,Caching Tasks>>
- <<build_cache.adoc#sec:task_output_caching_details,Cacheable Tasks>>
- <<build_cache_concepts.adoc#non_cacheable_tasks,Non-cacheable Tasks>>

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:caching,#caching>>`

[[avoid_provider_get_outside_task_action]]
== Do not call `get()` on a Provider outside a Task action

When configuring tasks and extensions do not call link:{javadocPath}/org/gradle/api/provider/Provider.html#get()[`get()`] on a provider, use link:{javadocPath}/org/gradle/api/provider/Provider.html#map(org.gradle.api.Transformer)[`map()`], or link:{javadocPath}/org/gradle/api/provider/Provider.html#flatMap(org.gradle.api.Transformer)[`flatMap()`] instead.

=== Explanation

A provider should be evaluated as late as possible.
Calling `get()` forces immediate evaluation, which can trigger unintended side effects, such as:

- The value of the provider becomes an input to configuration, causing potential configuration cache misses.
- The value may be evaluated too early, meaning you might not be using the final or correct value of the property. This may lead to painful and hard to debug ordering issues.
- It breaks Gradle’s ability to build dependencies and to track task inputs and outputs, making automatic task dependency wiring impossible. See <<lazy_configuration.adoc#working_with_task_dependencies_in_lazy_properties,Working with task inputs and outputs>>

It is preferable to avoid explicitly evaluating a `Provider` at all, and deferring to `map`/`flatMap` to connect `Providers` to `Providers` implicitly.

=== Example

Here is a task that writes an input `String` to a file:

====
include::sample[dir="snippets/best-practices/avoidProviderGetOutsideTaskAction-avoid/kotlin",files="build.gradle.kts[tags=provider-get-task-setup]"]
include::sample[dir="snippets/best-practices/avoidProviderGetOutsideTaskAction-avoid/groovy",files="build.gradle[tags=provider-get-task-setup]"]
====

<1> Using `Provider.get()` in the task action
<2> Gradle property that we wish to use as input

==== Don't Do This

You could call `get()` at configuration time to set up this task:

====
include::sample[dir="snippets/best-practices/avoidProviderGetOutsideTaskAction-avoid/kotlin",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/avoidProviderGetOutsideTaskAction-avoid/groovy",files="build.gradle[tags=avoid-this]"]
====

<1> *Reading the value of `currentEnvironment` at configuration time*: This value might change by the time the task start executing.
<2> *Reading the value of `buildDirectory` at configuration time*: This value might change by the time the task start executing.

==== Do This Instead

Instead, you should explicitly wire task inputs and outputs like this:

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidProviderGetOutsideTaskAction-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidProviderGetOutsideTaskAction-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

====
include::sample[dir="snippets/best-practices/avoidProviderGetOutsideTaskAction-do/kotlin",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/avoidProviderGetOutsideTaskAction-do/groovy",files="build.gradle[tags=do-this]"]
====

<1> *Using `map()` to transform `currentEnvironment`*: `map` transform runs only when the value is read.
<2> *Using `file()` to create a new `Provider<RegularFile>`*: the value of the `buildDirectory` is only checked when the value of the provider is read.

=== References

- <<incremental_build.adoc#sec:task_input_output_side_effects,Task Inputs and Outputs>>

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:inputs-and-outputs,#inputs-and-outputs>>`

[[group_describe_tasks]]
== Group and Describe custom Tasks

When defining custom task types or registering ad-hoc tasks, always set a clear `group` and `description`.

=== Explanation

A good group name is short, lowercase, and reflects the purpose or domain of the task.
For example: `documentation`, `verification`, `release`, or `publishing`.

Before creating a new group, look for an existing group name that aligns with your task’s intent.
It’s often better to reuse an established category to keep the task output organized and familiar to users.

This information is used in the <<command_line_interface.adoc#sec:listing_tasks,Tasks Report>> (shown via `./gradlew tasks`) to group and describe available tasks in a readable format.

Providing a group and description ensures that your tasks are:

* Displayed clearly in the report
* Categorized appropriately
* Understandable to other users (and to your future self)

NOTE: Tasks with no group are hidden from the <<command_line_interface.adoc#sec:listing_tasks,Tasks Report>> unless `--all` is specified.

=== Example

==== Don't Do This

Tasks without a group appear under the "other" category in `./gradlew tasks --all` output, making them harder to locate:

====
include::sample[dir="snippets/best-practices/groupTasks-avoid/kotlin",files="app/build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/groupTasks-avoid/groovy",files="app/build.gradle[tags=avoid-this]"]
====

[source,text]
----
$ gradlew :app:tasks --all
include::{snippetsPath}/best-practices/groupTasks-avoid/tests/groupTasks.bad.out[]
----

==== Do this Instead

When defining custom tasks, always assign a clear `group` and `description`:

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/groupTasks-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/groupTasks-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

====
include::sample[dir="snippets/best-practices/groupTasks-do/kotlin",files="app/build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/groupTasks-do/groovy",files="app/build.gradle[tags=do-this]"]
====

[source,text]
----
$ gradlew :app:tasks --all
include::{snippetsPath}/best-practices/groupTasks-do/tests/groupTasks.good.out[]
----

=== References

- <<more_about_tasks.adoc#sec:task_groups,Task Group and Description>>

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`

[[avoid_eager_file_collection_apis]]
== Avoid using eager APIs on File Collections

When working with Gradle's file collection types, be careful to avoid triggering dependency resolution during the configuration phase.

=== Explanation

Gradle's link:{javadocPath}/org/gradle/api/artifacts/Configuration.html[`Configuration`] and link:{javadocPath}/org/gradle/api/file/FileCollection.html[`FileCollection`] types extend the JDK’s `Collection<File>` interface.

However, calling some available methods from this interface—such as `.size()`, `.isEmpty()`, `getFiles()`, `asPath()`, or `.toList()`—on these Gradle types will implicitly trigger resolution of their dependencies.
The same is possible using Kotlin stdlib collection extension methods or Groovy GDK collection extensions.
Converting a `Configuration` to a `Set<File>` also discards any implicit task dependencies it carries.

You should avoid using these methods when configuring your build.
Instead, use the methods defined directly on the Gradle interfaces - this is a necessary _first step_ towards preventing eager resolutions.
Be sure to use <<lazy_configuration.adoc#working_with_files_in_lazy_properties,lazy types and APIs>> that defer resolution to wire task dependencies and inputs correctly.
Some methods that cause resolution are not obvious.
Be sure to check the actual behavior when using configurations in an atypical way.

=== Example

==== Don't Do This

====
include::sample[dir="snippets/best-practices/avoidJdkCollectionApiOnFileCollections-avoid/kotlin/lib",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/avoidJdkCollectionApiOnFileCollections-avoid/groovy/lib",files="build.gradle[tags=avoid-this]"]
====

<1> *`isEmpty()` causes resolution*: Many seemingly harmless Collection API methods like `isEmpty()` cause Gradle to resolve dependencies.
<2> *Accessing files directly*: Using `getFiles()` to access the files in a `Configuration` will also cause Gradle to resolve the file collection.
<3> *Adding a file via plus operator*: Using the plus operator will force the `runtimeClasspath` configuration to be resolved implicitly.
The implementation of `Configuration` doesn't override the plus operator for regular files, therefore it falls back to using the eager API, which causes resolution.
<4> *Be careful with indirect inputs*: Some built-in tasks, for example subtypes of `AbstractCopyTask` like `Zip`, allow adding inputs indirectly and can have the same problems.

==== Do This Instead

To avoid issues, always defer resolution until the execution phase.
Use APIs that support lazy evaluation.

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidJdkCollectionApiOnFileCollections-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidJdkCollectionApiOnFileCollections-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

====
include::sample[dir="snippets/best-practices/avoidJdkCollectionApiOnFileCollections-do/kotlin/lib",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/avoidJdkCollectionApiOnFileCollections-do/groovy/lib",files="build.gradle[tags=do-this]"]
====

<1> *Add configurations to Task properties or Specs directly*: This will defer resolution until the task is executed.
<2> *Add files to Specs separately*: This allows combining files with file collections without triggering implicit resolutions.

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:inputs-and-outputs,#inputs-and-outputs>>`, `<<tags_reference.adoc#tag:configurations,#configurations>>`

[[dont_resolve_configurations_before_task_execution]]
== Don't resolve Configurations before Task Execution

Resolving configurations before the task execution phase can lead to incorrect results and slower builds.

=== Explanation

Resolving a configuration - either directly via calling its link:{javadocPath}/org/gradle/api/artifacts/Configuration.html#resolve()[`resolve()`] method or indirectly via accessing its set of artifacts - returns a set of files that does not preserve references to the tasks that produced those files.

Configurations _are_ file collections and can be added to `@InputFiles` properties on other tasks.
It is important to do this correctly to avoid breaking automatic task dependency wiring between a consumer task and any tasks that are implicitly required to produce the artifacts being consumed.
For example, if a configuration contains a project dependency, Gradle knows that consumers of the configuration must first run any tasks that produce that project's artifacts.

In addition to correctness concerns, resolving configurations during the configuration phase can slow down the build, even when running unrelated tasks (e.g., `help`) that don't require the resolved dependencies.

=== Example

==== Don't Do This

====
include::sample[dir="snippets/best-practices/avoidResolvingConfigurationsManually-avoid/kotlin/app",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/avoidResolvingConfigurationsManually-avoid/groovy/app",files="build.gradle[tags=avoid-this]"]
====

<1> *Add project dependency*: The  `:lib` project must be built in order to resolve the runtime classpath successfully.
<2> *Declare input property as Set of files*: A simple `Set` input doesn't track task dependencies.
<3> *Dependency artifacts are used to calculate digest*: Artifacts from the already resolved classpath are used to calculate the digest.
<4> *Resolve runtimeClasspath*: The implicit task dependency on `:library:jar` task is lost here when the configuration is resolved prior to task execution.
The `lib` project will not be built when the `:app:badClasspathPrinter` task is run, leading to a failure in `calculateDigest` because the `lib.jar` file will not exist.


==== Do This Instead

To avoid issues, always defer resolution to the execution phase by using lazy APIs like link:{javadocPath}/org/gradle/api/file/FileCollection.html[FileCollection].

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidResolvingConfigurationsManually-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidResolvingConfigurationsManually-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

====
include::sample[dir="snippets/best-practices/avoidResolvingConfigurationsManually-do/kotlin/app",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/avoidResolvingConfigurationsManually-do/groovy/app",files="build.gradle[tags=do-this]"]
====

<1> *Write to a file in the output directory*: This is the same.
<2> *Declare input files property as ConfigurableFileCollection*: This lazy collection type will track task dependencies.
<3> *Dependency artifacts are resolved to calculate digest*: The classpath will be resolved at execution time to calculate the digest.
<4> *Configuration is passed to input property directly*: Using `from` causes the configuration to be lazily wired to the input property.
The configuration will be resolved when necessary, preserving task dependencies.
The output reveals that the `lib` project is now built when the `:app:goodClasspathPrinter` task is run because of the implicit task dependency, and the `lib.jar` file is found when calculating the digest.

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:inputs-and-outputs,#inputs-and-outputs>>`, `<<tags_reference.adoc#tag:configurations,#configurations>>`


[[default_path_sensitivities]]
== Use `@PathSensitivity.NONE` for file inputs and `@PathSensitivity.RELATIVE` for directories

Use link:{javadocPath}/org/gradle/api/tasks/PathSensitivity.html#NONE[`@PathSensitivity.NONE`] for file inputs and link:{javadocPath}/org/gradle/api/tasks/PathSensitivity.html#RELATIVE[`@PathSensitivity.RELATIVE`] for directory inputs.

=== Explanation

Tasks should generally care about the **contents** of their input files, not their location on disk.

When annotating file-based input properties (for example, `@InputFile` or `@InputFiles` collections), use `@PathSensitivity.NONE`.
This tells Gradle to ignore the path and only consider the file contents when determining whether a task is up-to-date.

For directory-based inputs (for example, `@InputDirectory` or `@InputFiles` collections), use `@PathSensitivity.RELATIVE`.
This tells Gradle to also consider only the name of the directory (ignoring its absolute location) and to relativize the paths of all files within that directory to it when doing up-to-date checks.

Using `PathSensitivity.NAME_ONLY` or `@PathSensitivity.ABSOLUTE` is generally incorrect.

`PathSensitivity.NAME_ONLY` tells Gradle to consider a file's name in addition to its contents, which is rarely useful.


`@PathSensitivity.ABSOLUTE` tells Gradle to consider a file's complete absolute path.
This prevents <<build_cache.adoc#build_cache,Build Cache>> and <<configuration_cache.adoc#config_cache,Configuration Cache>> hits across different machines or checkout locations, making your build non-relocatable.
It can also lead to confusing behavior where the same build produces different task outcomes when run from different directories.
If no `@PathSensitive` annotation is provided, `PathSensitivity.ABSOLUTE` is the default.

=== Example

==== Don't Do This

====
include::sample[dir="snippets/best-practices/preferPathSensitivityNone-avoid/kotlin",files="build.gradle.kts[]"]
include::sample[dir="snippets/best-practices/preferPathSensitivityNone-avoid/groovy",files="build.gradle[]"]
====

<1> The `AnimalSearchTask` task type uses a file input property annotated with `@PathSensitivity.ABSOLUTE`.  This means that the absolute path of the input file is used to determine if the task is `UP-TO-DATE` or if it can be loaded from cache.  Yet the path is irrelevant for the operation of the task's `@TaskAction`, which only cares about file contents.
<2> The `copy` task will move the _exact same_ `candidates.txt` to different destination directories, depending on if the `useAlternateInput` project property is set.
<3> The `search` task is wired to use as input whatever the file the `copy` task moved to its `destinationDir`. Despite the contents of the file being the same, when enabling the `-PuseAlternateInput` after a successful build, the `search` task will be out-of-date due to its different directory, and the search will be rerun.

==== Do this Instead

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/preferPathSensitivityNone-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/preferPathSensitivityNone-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

====
include::sample[dir="snippets/best-practices/preferPathSensitivityNone-do/kotlin",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/preferPathSensitivityNone-do/groovy",files="build.gradle[tags=do-this]"]
====

<1> Everything remains the same, except that the input property is now annotated with `@PathSensitivity.NONE`.  Only the contents of this input file matter to this task.  When the `search` task is rerun with `-PuseAlternateInput`, it remains `UP-TO-DATE`.

=== References

- <<build_cache_concepts.adoc#relocatability,Input Relocatability>>

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:inputs-and-outputs,#inputs-and-outputs>>`

[[use_unique_output_files_and_directories]]
== Use unique output files and directories

Overlapping output files or directories cause tasks to rerun unnecessarily and waste work.

=== Explanation

Gradle tracks all output files and directories declared by tasks to decide whether a task needs to be rerun.
For example, if the contents of a task’s output directory change after its last execution, Gradle will rerun that task.

Ensuring that each task uses its own unique output files and directories, both within a project and across the entire build, prevents unnecessary work.

=== Example

==== Don't Do This

====
include::sample[dir="snippets/best-practices/uniqueOutputs-avoid/kotlin",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/uniqueOutputs-avoid/groovy",files="build.gradle[tags=avoid-this]"]
====

<1> *Write to a file in the output directory*: This task produces a single file in the `outputDirectory`, named based on the `type` input.
<2> *Read a specific file in the input directory*: This task only needs to read a single `a.txt` file in the input directory.
<3> *Set output directory*: Sets `outputDirectory` to a subdirectory in `buildDirectory`.
<4> *Set output directory*: Same as above, using the same shared `greetings` directory.
<5> *Wire `greeterA` to consumer*: Makes sure that `greeterA` runs and produces the output directory before it is used by `consumer`.

With this setup, if you run the `consumer` task, then `greeterB`, the `consumer` task will be invalidated.

The next time `consumer` is run it will *not* be `UP-TO-DATE` and will have to run again despite not using the output from `greeterB`.

This happens because `greeterB` changes the contents of the shared output directory `greetings`, which is an output of `greeterA` that `consumer` depends on (despite `consumer` only actually using the unchanged `a.txt` file in that directory).

==== Do This Instead

To avoid issues, avoid using shared task output directories and files.

Instead, tasks should only declare the exact outputs and consume the exact inputs that they actually produce and consume.

The _simplest_ change to make here is to use distinct output directories for each `GreetingTask`.
This alone is sufficient to fix the problem.

====
include::sample[dir="snippets/best-practices/uniqueOutputs-simple-do/kotlin",files="build.gradle.kts[tags=simple-do-this]"]
include::sample[dir="snippets/best-practices/uniqueOutputs-simple-do/groovy",files="build.gradle[tags=simple-do-this]"]
====

<1> *Set unique output directories*: Each `GreetingTask` is assigned its own unique output directory based on the `type` input.

Now when running `consumer` task, then `greeterB`, then `consumer` task remains `UP-TO-DATE` as Gradle knows that it is not using the output from `greeterB`, since `greeterA` and `greeterB` write to distinct output directories.

However, a more _complete and idiomatic_ approach realizes that:

1. Tasks that produce single output files should make this clear from the type of their `@Output` properties.
2. Tasks that only consume single input files should make this clear from the type of their `@Input` properties.

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/uniqueOutputs-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/uniqueOutputs-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

====
include::sample[dir="snippets/best-practices/uniqueOutputs-do/kotlin",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/uniqueOutputs-do/groovy",files="build.gradle[tags=do-this]"]
====

<1> *Write to a specific output file*: This task produces a single file to a directly specified `outputFile` without registering an entire output directory.
<2> *Read a specific file*: Unlike the previous example the input is a single directly specified `inputFile` file.
<3> *Set output file*: Sets `outputFile` to a file that is inside a `shared` subdirectory of `buildDirectory`.
<4> *Wire `greeterA` to consumer*: Makes sure that `greeterA` produces the output file before it is used by `consumer` by wiring task inputs to outputs directly.

Now when running `consumer` task, then `greeterB`, the `consumer` task remains `UP-TO-DATE` as Gradle knows that it is not using the output from `greeterB`, since `greeterA` and `greeterB` _produce and consume distinct files_ (that happen to be in created in the same directory).

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:inputs-and-outputs,#inputs-and-outputs>>`

[[dont_hardcode_task_names]]
== Don't hardcode Task names unless they are documented as Public API

Hardcoding task names can make a build fragile when upgrading Gradle or third-party plugins.

=== Explanation

Some task names in Gradle are part of the <<public_apis.adoc#public_gradle_apis,Gradle public API>> and are stable.
Most other task names in Gradle and third-party plugins are internal implementation details. They may be renamed or removed in future versions and should not be relied upon.
Task types are also not guaranteed to remain stable; plugin authors may refactor them.

In practice, configuring tasks by type is usually more robust than depending on specific names, especially in ecosystems like Android or Kotlin where many tasks are generated dynamically.

Prefer, in order:

1. *Plugin DSL* - Configure tasks via extension blocks provided by a plugin that are automatically wired to the tasks that plugin creates.  This is the most future-proof and stable option.
2. *Task types* - More robust than names in many cases, but can still change across plugin versions, depending on the plugin's backwards compatibility policies.
3. *Task names* - Use only when they are explicitly documented as <<public_apis.adoc#public_gradle_apis,public API>>.

As a plugin author, provide a DSL extension for users to configure behavior declaratively. Internally wire the configuration into your tasks. Avoid exposing task names as part of your public API unless you are prepared to maintain them with deprecation cycles.

=== Example

==== Don't Do This

====
include::sample[dir="snippets/best-practices/avoidHardcodingTaskNames-avoid/kotlin",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/avoidHardcodingTaskNames-avoid/groovy",files="build.gradle[tags=avoid-this]"]
====

<1> Looking up `compileJava` by name: This relies on the hardcoded `"compileJava"` string.
<2> Using `tasks.named` to get the `generatePomFileForMavenPublication` task: This unnecessarily relies on this task's name, even though it's the only `GenerateMavenPom` task that needs configuring.

==== Do This Instead

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidHardcodingTaskNames-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/avoidHardcodingTaskNames-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

A better option for the `compileJava` configuration is to use a public constant — this task name is part of <<public_apis.adoc#public_gradle_apis,public API>>, so referencing it via the constant is safe:

====
include::sample[dir="snippets/best-practices/avoidHardcodingTaskNames-do/kotlin",files="build.gradle.kts[tags=ok]"]
include::sample[dir="snippets/best-practices/avoidHardcodingTaskNames-do/groovy",files="build.gradle[tags=ok]"]
====

<1> Using `tasks.named` with `JavaPlugin.COMPILE_JAVA_TASK_NAME`: Replaces the hardcoded string with a public constant.

The best option is to use the plugin DSL where possible — this avoids referring to tasks by name or type at all:

====
include::sample[dir="snippets/best-practices/avoidHardcodingTaskNames-do/kotlin",files="build.gradle.kts[tags=best]"]
include::sample[dir="snippets/best-practices/avoidHardcodingTaskNames-do/groovy",files="build.gradle[tags=best]"]
====

<1> Using Java DSL: Entirely avoids implementation details of `java-library` plugin and sets `sourceCompatibility` for all `JavaCompile` tasks.
<2> Using publishing DSL: Avoids hardcoding task names and ensures that the generated POM has the expected URL.

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:upgrades,#upgrades>>`

[[dont_access_project_instance_inside_task]]
== Don't access a `Project` instance during Task Execution

Do not access a `Project` instance inside a task action or task property.

=== Explanation

Accessing the `Project` instance during task execution is not compatible with the <<configuration_cache.adoc#config_cache,Configuration Cache>> and should be avoided.
Alternatives exist for almost every use case involving data or operations from the `Project` object:

- Specify the data you were previously retrieving from the `Project` instance as an explicit task input.
- Use similar functionality already exposed through the `Task` object (e.g., `Task.getLogger()` instead of `Project.getLogger()`).
- Capture `Project` values in a local variable during task configuration, which can then be referenced during execution.

NOTE: Accessing the `Project` instance during _task configuration_ is expected and safe — this is how you wire task input properties to project properties.
The problem arises when `Project` is accessed during _task execution_ (e.g., inside `@TaskAction` or `doLast`).

=== Example

==== Don't Do This

====
include::sample[dir="snippets/best-practices/dontAccessProject-avoid/kotlin",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/dontAccessProject-avoid/groovy",files="build.gradle[tags=avoid-this]"]
====

<1> *Reading `project.version` in a task action*: Inside the task's action, the `Project` instance is accessed to read the `version` property.

There are two main problems with this setup:

1. Accessing the `Project` instance during task execution will cause Configuration Cache failures.
2. Because the version is not declared as a task input, Gradle cannot track it. This leads to incorrect up-to-date results if the project version changes.

==== Do This Instead

++++
<div style="text-align: right;">
  <a class="download-project-link"
     data-base-path="https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/dontAccessProject-do/"
     href="https://download-directory.github.io/?url=https://github.com/gradle/gradle/tree/master/platforms/documentation/docs/src/snippets/best-practices/dontAccessProject-do/kotlin">
    <img src="https://img.shields.io/badge/Download%20Project-GitHub-blue?logo=github&style=flat" alt="Download"/>
  </a>
</div>
++++

To ensure compatibility, avoid accessing the `Project` instance during task execution.
Instead, tasks should explicitly declare all required inputs.

====
include::sample[dir="snippets/best-practices/dontAccessProject-do/kotlin",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/dontAccessProject-do/groovy",files="build.gradle[tags=do-this]"]
====

<1> *Declare the version as an input property*: This allows Gradle to track the version string for up-to-date checks.
<2> *Assign the version during configuration*: It is safe to read `project.version` during the configuration phase to assign it to a task input.
<3> *Set the output location*: Use `project.layout` during configuration to define the task's output directory.

Now, when running the task, there are no "hidden inputs" that Gradle cannot track.
This ensures that up-to-date checks accurately determine when a task needs to run again.
Additionally, because the `Project` object is not accessed after configuration, the Configuration Cache will function correctly.

=== References

- <<configuration_cache_requirements.adoc#config_cache:requirements:use_project_during_execution,Configuration Cache Requirements>>

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:inputs-and-outputs,#inputs-and-outputs>>`

[[map_versus_flatmap]]
== Wire lazy task outputs using `map` and `flatMap`

Use `flatMap` to extract a `Provider`-typed output from a task, and `map` to transform the resulting value.
Together, they preserve the task dependency chain so Gradle knows which tasks must run first.

=== Explanation

When wiring task outputs to inputs, `flatMap` extracts a `Provider`-typed output from a task while preserving the task dependency that the output `Provider` carries.
Then `map` transforms that value.
For example, reading a file's contents or extracting a path, without breaking the chain.
The typical pattern looks like:

====
[.multi-language-sample]
=====
[source,kotlin]
----
consumer.inputContent.set(
    producer.flatMap { it.outputFile }       // extract the Provider, keeping the task dependency
        .map { it.asFile.readText() }        // transform the value lazily at execution time
)
----
=====
[.multi-language-sample]
=====
[source,groovy]
----
consumer.inputContent.set(
    producer.flatMap { it.outputFile }       // extract the Provider, keeping the task dependency
        .map { it.asFile.text }              // transform the value lazily at execution time
)
----
=====
====

For dependency tracking to work, task outputs should be directly annotated abstract getters:

====
[.multi-language-sample]
=====
[source,kotlin]
----
@get:OutputFile
abstract val outputFile: RegularFileProperty
----
=====
[.multi-language-sample]
=====
[source,groovy]
----
@OutputFile
abstract RegularFileProperty getOutputFile()
----
=====
====

With this pattern, `flatMap` reliably carries the task dependency and `map` safely transforms the result.

A few specific patterns break this chain:

1. **Do not call `.get()` inside `map` or `flatMap`** — the file doesn't exist yet at configuration time, breaking lazy configuration and task dependency tracking
2. **Do not use standalone `provider {}` to wrap task outputs** — `provider {}` produces a disconnected provider with no task dependency information
3. **Use directly annotated abstract getters for task outputs** — `flatMap` can only track dependencies through directly annotated abstract getters, not task outputs that exist only to define transforms of other properties. Getters that return `Provider` instead of `Property` also break tracking, even when annotated.
4. **Use `map` for plain values, `flatMap` for `Provider`-typed outputs** — `flatMap` requires its closure to return a `Provider`; use `map` to extract `File` or `String` values

==== 1. Do not call `.get()` inside `map` or `flatMap`

Calling `.get()` inside `map` to read file content fails because the file does not exist yet at configuration time — the producer task has not run.
This pattern also breaks the Configuration Cache.

Both examples below use the same `generatorTask` producer; only the consumer differs.

===== Don't Do This

====
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-avoid/kotlin",files="build.gradle.kts[tags=config-time-read]"]
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-avoid/groovy",files="build.gradle[tags=config-time-read]"]
====
<1> *File read at configuration time*: `.get()` realizes the file before the producer task has run; reading it here fails because the file doesn't exist yet.

===== Do This Instead

Use the `flatMap`/`map` chain instead.
The `map` on a `flatMap` result is evaluated lazily at execution time, when the file exists:

====
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/kotlin",files="build.gradle.kts[tags=flatmap-provider]"]
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/groovy",files="build.gradle[tags=flatmap-provider]"]
====
<1> *Lazy wiring via `flatMap`*: extracts the `outputFile` Provider while preserving the dependency on `generator`.
<2> *Read content lazily*: chaining `map` on the `flatMap` result defers reading until execution time.

This preserves the task dependency because the provider chain remains connected to the original task.

==== 2. Do not use standalone `provider {}` to wrap task outputs

A standalone `provider {}` creates a provider with no connection to any task.
Gradle cannot determine that it depends on the producer task, so the producer will not be added to the task graph.

Both examples below use the same `generatorTask` producer; only the consumer differs.

===== Don't Do This

====
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-ex-avoid/kotlin",files="build.gradle.kts[tags=naked-provider]"]
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-ex-avoid/groovy",files="build.gradle[tags=naked-provider]"]
====
<1> *Disconnected `provider {}` block*: has no link to the generator task; Gradle cannot infer that `consumeNaked` depends on `generator` from this provider alone.

===== Do This Instead

Use `flatMap` to extract the `Provider`-typed output, then chain `map` to transform the value:

====
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/kotlin",files="build.gradle.kts[tags=flatmap-provider]"]
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/groovy",files="build.gradle[tags=flatmap-provider]"]
====
<1> *Lazy wiring via `flatMap`*: extracts the `outputFile` Provider while preserving the dependency on `generator`.
<2> *Read content lazily*: chaining `map` on the `flatMap` result defers reading until execution time.

==== 3. Use directly annotated abstract getters for task outputs

When a task's output is computed via `.map {}` rather than directly annotated with `@OutputFile`, `flatMap` may extract the mapped provider without preserving the task dependency.

===== Don't Do This

====
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-avoid/kotlin",files="build.gradle.kts[tags=derived-property]"]
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-avoid/groovy",files="build.gradle[tags=derived-property]"]
====
<1> *Derived output property*: when `outputFile` is built via `.map { ... }` rather than annotated directly, `flatMap` may lose the task dependency.

===== Do This Instead

As a workaround, use `.map { it.property.get() }` to preserve the dependency:

====
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/kotlin",files="build.gradle.kts[tags=derived-property-fix]"]
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/groovy",files="build.gradle[tags=derived-property-fix]"]
====
<1> *Workaround with `.map { it.property.get() }`*: preserves the task dependency at the cost of an explicit `.get()` call.

The preferred approach is to use directly annotated abstract getters instead of derived ones, so that `flatMap` works reliably:

====
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/kotlin",files="build.gradle.kts[tags=direct-annotation]"]
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/groovy",files="build.gradle[tags=direct-annotation]"]
====
<1> *Directly annotated abstract getter*: with `@OutputFile` on an abstract `RegularFileProperty`, `flatMap` carries the task dependency reliably.

==== 4. Use `map` for plain values, `flatMap` for `Provider`-typed outputs

When a task uses old-style getters (like `File`) instead of `Provider`-typed outputs, use `map` instead of `flatMap` to extract the value.
The `flatMap` closure must return a `Provider`; wrapping a plain value in a standalone `provider {}` would compile, but — as in item 2 — it would drop the task dependency.

===== Do This

====
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/kotlin",files="build.gradle.kts[tags=eager-property]"]
include::sample[dir="snippets/best-practices/beCarefulWithMapFlatmap-do/groovy",files="build.gradle[tags=eager-property]"]
====
<1> *Use `map` for non-Provider outputs*: when the producer exposes a plain `File` instead of a Provider, `map` is the right extractor.

=== References

- <<lazy_configuration.adoc#working_with_task_dependencies_in_lazy_properties,Working with task inputs and outputs>>

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:task-configuration-avoidance,#task-configuration-avoidance>>`

[[favor_collection_properties]]
== Favor collection property types over a `Property` holding a collection

Declare collection-valued properties as `ListProperty<T>`, `SetProperty<T>`, or `MapProperty<K, V>` rather than wrapping a collection inside a plain `Property`, such as a `Property<Collection<T>>`.

=== Explanation

A `Property` treats its value as a single, opaque unit.
When that value happens to be a collection, Gradle cannot see the elements inside it, so the only way to change the property is to replace the entire collection.

The dedicated collection types lift the collection into the property itself, so Gradle understands its structure.
This is what makes incremental composition possible:

* *Contributions are additive:* `add(T)`, `addAll(Iterable<T>)`, and `put(K, V)` let a plugin, a convention plugin, and a build script each contribute independently, without knowing what the others contributed.
* *Individual elements can be lazy:* `add(Provider<T>)` accepts a provider for a _single element_. If that provider is a task output, the collection property carries the producing task's dependency automatically, so no explicit `dependsOn` is required.
* *No eager reads are needed:* Appending to a plain `Property` forces you to call `get()` at configuration time to read the current collection before writing a new one back, which violates <<avoid_provider_get_outside_task_action,Do not call `get()` on a Provider outside a Task action>>.

Gradle rejects the most common forms of this mistake outright.
Creating a `Property<List<T>>`, `Property<Set<T>>`, or `Property<Map<K, V>>` fails with an error directing you to the corresponding collection type.

However, several equivalent declarations are _not_ rejected and are just as wrong:

* `Property<Collection<T>>` and `Property<Iterable<T>>`
* `Property<T[]>`, a property holding an array
* A plain, non-lazy `List<T>` or `Map<K, V>` field with a getter and setter

Treat all of these the same way, and prefer the collection property type that matches the data.
For file-valued collections, use `ConfigurableFileCollection` instead.

=== Example

==== Don't Do This

====
include::sample[dir="snippets/best-practices/favorCollectionProperties-avoid/kotlin",files="build.gradle.kts[tags=setup]"]
include::sample[dir="snippets/best-practices/favorCollectionProperties-avoid/groovy",files="build.gradle[tags=setup]"]
====

<1> *`WriteVersion` task*: writes the version string `"1.0"` to `versionFile` at execution time.

====
include::sample[dir="snippets/best-practices/favorCollectionProperties-avoid/kotlin",files="build.gradle.kts[tags=avoid-this]"]
include::sample[dir="snippets/best-practices/favorCollectionProperties-avoid/groovy",files="build.gradle[tags=avoid-this]"]
====

<1> *A `Property` cannot expose its elements*: `Collection<String>` is an opaque value here, so there is no `add` method and no way to contribute a single message.
Gradle would reject `Property<List<String>>` outright, but `Property<Collection<String>>` is accepted despite being the same mistake.
<2> *Appending requires an eager read*: the only way to add a message is to `get()` the current collection at configuration time, copy it, and write the result back.
This resolves the property before the build has finished configuring, in violation of <<avoid_provider_get_outside_task_action,Do not call `get()` on a Provider outside a Task action>>.
<3> *A lazy contribution destroys the others*: there is no way to _add_ a single `writeVersion`-derived element to a `Property<Collection<T>>`.
The only way to consume a task output is to replace the entire property with a `Provider<Collection<T>>`, silently discarding every other contribution.

The two earlier messages are silently lost, and the build does not fail or warn:

----
include::{snippetsPath}/best-practices/favorCollectionProperties-avoid/tests/favorCollectionProperties-avoid.out[]
----

==== Do This Instead

====
include::sample[dir="snippets/best-practices/favorCollectionProperties-do/kotlin",files="build.gradle.kts[tags=do-this]"]
include::sample[dir="snippets/best-practices/favorCollectionProperties-do/groovy",files="build.gradle[tags=do-this]"]
====

<1> *`ListProperty` exposes `add` and `addAll`*: contributors can append elements directly instead of replacing the whole collection.
<2> *Contributors are independent*: `add` appends a message without reading, copying, or replacing the existing collection, so each caller can ignore the others.
<3> *Elements can be providers*: `add(Provider<T>)` contributes a single lazily computed message.
The provider carries the dependency on `writeVersion`, so `report` runs after it without an explicit `dependsOn`.

All three messages survive:

----
include::{snippetsPath}/best-practices/favorCollectionProperties-do/tests/favorCollectionProperties-do.out[]
----

=== References

- <<lazy_configuration.adoc#working_with_collections,Working with Collections>>
- <<lazy_configuration.adoc#working_with_maps,Working with Maps>>
- <<properties_providers.adoc#managed_properties,Managed Properties>>

=== Tags

`<<tags_reference.adoc#tag:tasks,#tasks>>`, `<<tags_reference.adoc#tag:properties,#properties>>`, `<<tags_reference.adoc#tag:inputs-and-outputs,#inputs-and-outputs>>`
