# Task Configuration Avoidance

version 7.6.6-20250920032403+0000

This chapter provides an introduction to "configuration avoidance" when dealing with tasks and explains some guidelines for migrating your build to use the configuration avoidance APIs effectively. The API described here co-exists with an existing API that will be replaced with our usual deprecation process over several major releases. As of Gradle 5.1, we recommend that the configuration avoidance APIs be used whenever tasks are created by custom plugins.

## How does the configuration avoidance API work?

In a nutshell, the API allows builds to avoid the cost of creating and configuring tasks during Gradle’s configuration phase when those tasks will never be executed. For example, when running a compile task, other unrelated tasks, like code quality, testing and publishing tasks, will not be executed, so any time spent creating and configuring those tasks is unnecessary. The configuration avoidance API avoids configuring tasks if they will not be needed during the course of a build, which can have a significant impact on total configuration time.

To avoid creating and configuring tasks, we say that a task is "registered" but not created. When a task is in this state, it is known to the build, it can be configured, and references to it can be passed around, but the task object itself has not actually been created, and none of its configuration actions have been executed. It will remain in this state until something in the build needs the instantiated task object (for instance if the task is executed on the command line or the task is a dependency of a task executed on the command line). If the task object is never needed, then the task will remain in the registered state, and the cost of creating and configuring the task will be avoided.

In Gradle, you register a task using TaskContainer.register(java.lang.String). There are variations of this method that allow providing a task type and/or an action for modifying the task configuration. Instead of returning a task instance, the `register(…​)` method returns a TaskProvider, which is a reference to the task that can be used in many places where a normal task object might be used (for example when creating task dependencies).

## Guidelines

### How do I defer task creation?

Effective task configuration avoidance requires build authors to change instances of TaskContainer.create(java.lang.String) to TaskContainer.register(java.lang.String).

Older versions of Gradle only support the `create(…​)` API. The `create(…​)` API eagerly creates and configures tasks when it is called and should be avoided.

Using `register(…​)` alone may not be enough to avoid all task configuration completely. You may need to change other code that configures tasks by name or by type, as explained in the following sections.

### How do I defer task configuration?

Eager APIs like DomainObjectCollection.all(org.gradle.api.Action) and DomainObjectCollection.withType(java.lang.Class, org.gradle.api.Action) will immediately create and configure any registered tasks. To defer task configuration, you will need to migrate to a configuration avoidance API equivalent. See the table below to identify the alternative.

### How do I reference a task without creating/configuring it?

Instead of referencing a task object, you can work with a registered task via a TaskProvider object. A TaskProvider can be obtained in several ways including when calling TaskContainer.register(java.lang.String) or using the TaskCollection.named(java.lang.String) method.

Calling Provider.get() or looking up a task by name with TaskCollection.getByName(java.lang.String) will cause the task to be created and configured. Methods like Task.dependsOn(java.lang.Object…​) and ConfigurableFileCollection.builtBy(java.lang.Object...) work with TaskProvider in the same way as Task, so you do not need to unwrap a `Provider` for explicit dependencies to continue to work.

If you are configuring a task by name, you will need to use the configuration avoidance equivalent. See the table below to identify the alternative.

### How to get an instance of a Task?

In the event you still need to get access to a Task instance, you can use TaskCollection.named(java.lang.String) and call Provider.get(). This will cause the task to be created/configured, but everything should work as it has with the eager APIs.

### How do I order tasks with configuration avoidance in mind?

| Calling ordering methods will not cause task creation by itself. All these methods do is declare relationships. However, it’s important to note, that the existence of these relationships might indirectly cause task creation in later stages of the build process.

When task relationships need to be established (i.e. `dependsOn`, `finalizedBy`, `mustRunAfter`, `shouldRunAfter`), a distinction can be made between soft, and strong relationships, as their effects on task creation in configuration time differ:

Task.mustRunAfter(…​) and Task.shouldRunAfter(…​) represent soft relationships, which can only change the order of existing tasks, but can’t trigger their creation.

Task.dependsOn(…​) and Task.finalizedBy(…​) represent strong relationships, which will force the execution of referenced tasks, even if they wouldn’t have been created otherwise.

- If a task is not executed (regardless if it was created with Task.register(…​) or Task.create(…​)), the defined relationships will not trigger any task creation at configuration time.

- If a task is executed, all strongly associated tasks need to be created and configured at configuration time, as they might have other `dependsOn` or `finalizedBy` relationships. This will happen transitively, until the task graph contains all strong relationships.

## Migration Guide

The following sections will go through some general guidelines to adhere to when migrating the build logic as well as the steps we recommend following. We also cover some troubleshooting and pitfalls to help you work around some issues you may encounter during the migration.

### General

1. Use `help` task as a benchmark during the migration. The `help` task is the perfect candidate to benchmark your migration process. In a build that uses only the configuration avoidance API, a build scan would show no tasks created immediately or created during configuration, and only the tasks that were actually executed would be created. Be mindful of the version of the build scan plugin in use.

2. Only mutate the current task inside a configuration action. Because the task configuration action can now run immediately, later or never, mutating anything other than the current task can cause indeterminate behavior in your build. Consider the following code:

       def check = tasks.register("check")
       tasks.register("verificationTask") { verificationTask ->
           // Configure verificationTask

           // Run verificationTask when someone runs check
           check.get().dependsOn verificationTask
       }

       val check by tasks.registering
       tasks.register("verificationTask") {
           // Configure verificationTask

           // Run verificationTask when someone runs check
           check.get().dependsOn(this)
       }

   Executing the `gradle check` task should execute `verificationTask`, but with this example, it won’t. This is because the dependency between `verificationTask` and `check` only happens when `verificationTask` is realized. To avoid issues like this, you must only modify the task associated with the configuration action. Other tasks should be modified in their own configuration action. The code would become:

       def check = tasks.register("check")
       def verificationTask = tasks.register("verificationTask") {
           // Configure verificationTask
       }
       check.configure {
           dependsOn verificationTask
       }

       val check by tasks.registering
       val verificationTask by tasks.registering {
           // Configure verificationTask
       }
       check {
           dependsOn(verificationTask)
       }

   In the future, Gradle will consider this sort of anti-pattern an error and will produce an exception.

3. Prefer small incremental changes. Smaller changes are easier to sanity check. If you ever break your build logic, it will be easier to analyze the changelog since the last successful verification.

4. Ensure a good plan is established for validating the build logic. Usually, a simple `build` task invocation should do the trick to validate your build logic. However, some builds may need additional verification — understand the behavior of your build and make sure you have a good plan for verification.

5. Prefer automatic testing to manual testing. It’s good practice to write integration test for your build logic using TestKit.

6. Avoid referencing a task by name. In the majority of cases, referencing a task by name is a fragile pattern and should be avoided. Although the task name is available on the `TaskProvider`, effort should be made to use references from a strongly typed model instead.

7. Use the new task API as much as possible. Eagerly realizing some tasks may cause a cascade of other tasks to be realized. Using `TaskProvider` helps create an indirection that protects against transitive realization.

8. Some APIs may be disallowed if you try to access them from the new API’s configuration blocks. For example, `Project.afterEvaluate()` cannot be called when configuring a task registered with the new API. Since `afterEvaluate` is used to delay configuring a `Project`, mixing delayed configuration with the new API can cause errors that are hard to diagnose because tasks registered with the new API are not always configured, but an `afterEvaluate` block may be expected to always execute.

### Migration Steps

The first part of the migration process is to go through the code and manually migrate eager task creation and configuration to use configuration avoidance APIs. The following explores the recommended steps for a successful migration. While going through these steps, keep in mind the guidelines above.

| Using the new API in a plugin will require users to use Gradle 4.9 or later. Plugin authors should refer to Supporting older versions of Gradle section.

1. Migrate task configuration that affects all tasks (`tasks.all {}`) or subsets by type (`tasks.withType(…​) {}`). This will cause your build to eagerly create fewer tasks that are registered by plugins.

2. Migrate tasks configured by name. Similar to the previous point, this will cause your build to eagerly create fewer tasks that are registered by plugins. For example, logic that uses `TaskContainer#getByName(String, Closure)` should be converted to `TaskContainer#named(String, Action)`. This also includes task configuration via DSL blocks.

3. Migrate tasks creation to `register(…​)`. At this point, you should change anywhere that you are creating tasks to instead register those tasks.

For all steps above, be aware of the common pitfalls around deferred configuration.

After making these changes, you should see improvement in the number of tasks that are eagerly created at configuration time. Use build scans to understand what tasks are still being created eagerly and where this is happening.

### Troubleshooting

- What tasks are being realized? As we keep developing the feature, more reporting, and troubleshooting information will be made available to answer this question. In the meantime, build scan is the best way to answer this question. Follow these steps:

  1. Create a build scan. Execute the Gradle command using the `--scan` flag.

  2. Navigate to the configuration performance tab.

     Figure 1. Navigate to configuration performance tab in build scan

     1. Navigate to the performance card from the left side menu.

     2. Navigate to the configuration tab from the top of the performance card.

  3. All the information requires will be presented.

     Figure 2. Configuration performance tab in build scan annotated

     1. Total tasks present when each task is created or not.

        - "Created immediately" represents tasks that were created using the eager task APIs.

        - "Created during configuration" represents tasks that were created using the configuration avoidance APIs, but were realized explicitly (via `TaskProvider#get()`) or implicitly using the eager task query APIs.

        - Both "Created immediately" and "Created during configuration" numbers are considered the "bad" numbers that should be minimized as much as possible.

        - "Created during task graph calculation" represents the tasks created when building the execution task graph. Ideally, this number would be equal to the number of tasks executed.

        - "Not created" represents the tasks that were avoided in this build session.

     2. The next section helps answer the question of where a task was realized. For each script, plugin or lifecycle callback, the last column represents the tasks that were created either immediately or during configuration. Ideally, this column should be empty.

     3. Focusing on a script, plugin, or lifecycle callback will show a break down of the tasks that were created.

### Pitfalls

- Beware of the hidden eager task realization. There are many ways that a task can be configured eagerly. For example, configuring a task using the task name and a DSL block will cause the task to immediately be created when using the Groovy DSL:

      // Given a task lazily created with
      tasks.register("someTask")

      // Some time later, the task is configured using a DSL block
      someTask {
          // This causes the task to be created and this configuration to be executed immediately
      }

  Instead use the `named()` method to acquire a reference to the task and configure it:

      tasks.named("someTask") {
          // ...
          // Beware of the pitfalls here
      }

  Similarly, Gradle has syntactic sugar that allows tasks to be referenced by name without an explicit query method. This can also cause the task to be immediately created:

      tasks.register("someTask")

      // Sometime later, an eager task is configured like
      task anEagerTask {
          // The following will cause "someTask" to be looked up and immediately created
          dependsOn someTask
      }

  There are several ways this premature creation can be avoided:

  - Use a `TaskProvider` variable. Useful when the task is referenced multiple times in the same build script.

        def someTask = tasks.register("someTask")

        task anEagerTask {
            dependsOn someTask
        }

        val someTask by tasks.registering

        task("anEagerTask") {
            dependsOn(someTask)
        }

  - Migrate the consumer task to the new API.

        tasks.register("someTask")

        tasks.register("anEagerTask") {
            dependsOn someTask
        }

  - Lookup the task lazily. Useful when the tasks are not created by the same plugin.

        tasks.register("someTask")

        task anEagerTask {
            dependsOn tasks.named("someTask")
        }

        tasks.register("someTask")

        task("anEagerTask") {
            dependsOn(tasks.named("someTask"))
        }

- The build scan plugin `buildScanPublishPrevious` task is eager until version 1.15. Upgrade the build scan plugin in your build to use the latest version.

#### Supporting older versions of Gradle

This section describes two ways to keep your plugin backward compatible with older version of Gradle if you must maintain compatibility with versions of Gradle older than 4.9. Most of the new API methods are available starting with Gradle 4.9.

| Although backward compatibility is good for users, we still recommended to upgrade to newer Gradle releases in a timely manner. This will reduce your maintenance burden.

The first method to maintain compatibility is to compile your plugin against the Gradle 4.9 API and conditionally call the right APIs with Groovy (example).

The second method is to use Java reflection to cope with the fact that the APIs are unavailable during compilation (example).

It is highly recommended to have cross-version test coverage using TestKit and multiple versions of Gradle.

## Existing vs New API overview

| Methods that take a `groovy.lang.Closure` are covered in the new API with methods taking `org.gradle.api.Action`. More convenience methods may be added in the future based on user feedback. Some old API methods may never have a direct replacement in the new API. Some APIs may be restricted when accessed in a configuration action registered via the configuration avoidance methods.

Old vs New API | Description
--- | ---
Instead of: `task myTask(type: MyTask) {}` | There is not a shorthand Groovy DSL for using the new API.
Use: `tasks.register("myTask", MyTask) {}`
Instead of: TaskContainer.create(java.util.Map) | Use one of the alternatives below.
Use: No direct equivalent.
Instead of: TaskContainer.create(java.util.Map, groovy.lang.Closure) | Use one of the alternatives below.
Use: No direct equivalent.
Instead of: TaskContainer.create(java.lang.String) | This returns a `TaskProvider` instead of a `Task`.
Use: TaskContainer.register(java.lang.String)
Instead of: TaskContainer.create(java.lang.String, groovy.lang.Closure) | This returns a `TaskProvider` instead of a `Task`.
Use: TaskContainer.register(java.lang.String, org.gradle.api.Action)
Instead of: TaskContainer.create(java.lang.String, java.lang.Class) | This returns a `TaskProvider` instead of a `Task`.
Use: TaskContainer.register(java.lang.String, java.lang.Class)
Instead of: TaskContainer.create(java.lang.String, java.lang.Class, org.gradle.api.Action) | This returns a `TaskProvider` instead of a `Task`.
Use: TaskContainer.register(java.lang.String, java.lang.Class, org.gradle.api.Action)
Instead of: TaskContainer.create(java.lang.String, java.lang.Class, java.lang.Object…​) | This returns a `TaskProvider` instead of a `Task`.
Use: TaskContainer.register(java.lang.String, java.lang.Class, java.lang.Object…​)
Instead of: TaskCollection.getByName(java.lang.String) | This returns a `TaskProvider` instead of a `Task`.
Use: TaskCollection.named(java.lang.String)
Instead of: TaskCollection.getByName(java.lang.String, groovy.lang.Closure) | This returns a `TaskProvider` instead of a `Task`.
Use: `named(java.lang.String, Action)`
Instead of: TaskContainer.getByPath(java.lang.String) | Accessing tasks from another project requires a specific ordering of project evaluation.
Use: No direct equivalent.
Instead of: NamedDomainObjectCollection.findByName(java.lang.String) | `named(String)` is the closest equivalent, but will fail if the task does not exist. Using `findByName(String)` will cause tasks registered with the new API to be created/configured.
Use: No direct equivalent.
Instead of: TaskContainer.findByPath(java.lang.String) | See `getByPath(String)` above.
Use: No direct equivalent.
Instead of: TaskCollection.withType(java.lang.Class) | This is OK to use because it does not require tasks to be created immediately.
Use: OK
Instead of: `withType(java.lang.Class).getByName(java.lang.String)` | This returns a `TaskProvider` instead of a `Task`.
Use: `named(java.lang.String, java.lang.Class)`
Instead of: DomainObjectCollection.withType(java.lang.Class, org.gradle.api.Action) | This returns `void`, so it cannot be chained.
Use: `withType(java.lang.Class).configureEach(org.gradle.api.Action)`
Instead of: DomainObjectCollection.all(org.gradle.api.Action) | This returns `void`, so it cannot be chained.
Use: DomainObjectCollection.configureEach(org.gradle.api.Action)
Instead of: TaskCollection.whenTaskAdded(org.gradle.api.Action) | This returns `void`, so it cannot be chained.
Use: DomainObjectCollection.configureEach(org.gradle.api.Action)
Instead of: DomainObjectCollection.whenObjectAdded(org.gradle.api.Action) | This returns `void`, so it cannot be chained.
Use: DomainObjectCollection.configureEach(org.gradle.api.Action)
Instead of: NamedDomainObjectSet.findAll(groovy.lang.Closure) | Avoid calling this method. `matching(Spec)` and `configureEach(Action)` are more appropriate in most cases.
Use: OK, with issues.
Instead of: TaskCollection.matching(groovy.lang.Closure) | `matching(Spec)` requires all tasks to be created, so try to limit the impact by restricting the type of task, like `withType(java.lang.Class).matching(Spec)`.
Use: OK, with issues.
Instead of: TaskCollection.getAt(java.lang.String) | Avoid calling this directly as it’s a Groovy convenience method. The alternative returns a `TaskProvider` instead of a `Task`.
Use: TaskCollection.named(java.lang.String)
Instead of: `iterator()` or implicit iteration over the `Task` collection | Avoid doing this as it requires creating and configuring all tasks. See `findAll(Closure)` above.
Use: OK, with issues.
Instead of: `remove(org.gradle.api.Task)` | Avoid calling this. The behavior of `remove` with the new API may change in the future.
Use: OK, with issues.
Instead of: TaskContainer.replace(java.lang.String) | Avoid calling this. The behavior of `replace` with the new API may change in the future.
Use: OK, with issues.
Instead of: TaskContainer.replace(java.lang.String, java.lang.Class) | Avoid calling this. The behavior of `replace` with the new API may change in the future.
Use: OK, with issues.