# Organizing Tasks

version 8.14.4-20260116040737+0000

There are two types of tasks, actionable and lifecycle tasks.

Actionable tasks in Gradle are tasks that perform actual work, such as compiling code. Lifecycle tasks are tasks that do not do work themselves. These tasks have no actions, instead, they bundle actionable tasks and serve as targets for the build.

A well-organized setup of lifecycle tasks enhances the accessibility of your build for new users and simplifies integration with CI.

## Lifecycle tasks

Lifecycle tasks can be particularly beneficial for separating work between users or machines (CI vs local). For example, a developer on a local machine might not want to run an entire build on every single change.

Let’s take a standard `app` as an example which applies the `base` plugin.

| The Gradle `base` plugin defines several lifecycle tasks, including `build`, `assemble`, and `check`.

We group the `build`, `check` task, and the `run` task by adding the following lines to the `app` build script:

app/build.gradle.kts

    tasks.build {
        group = myBuildGroup
    }

    tasks.check {
        group = myBuildGroup
        description = "Runs checks (including tests)."
    }

    tasks.named("run") {
        group = myBuildGroup
    }

app/build.gradle

    tasks.build {
        group = myBuildGroup
    }

    tasks.check {
        group = myBuildGroup
        description = "Runs checks (including tests)."
    }

    tasks.named('run') {
        group = myBuildGroup
    }

If we now look at the `app:tasks` list, we can see the three tasks are available:

    $ ./gradlew :app:tasks

    > Task :app:tasks

    ------------------------------------------------------------
    Tasks runnable from project ':app'
    ------------------------------------------------------------

    My app build tasks
    ------------------
    build - Assembles and tests this project.
    check - Runs checks (including tests).
    run - Runs this project as a JVM application
    tasksAll - Show additional tasks.

This is already useful if the standard lifecycle tasks are sufficient. Moving the groups around helps clarify the tasks you expect to used in your build.

In many cases, there are more specific requirements that you want to address. One common scenario is running quality checks without running tests. Currently, the `:check` task runs tests and the code quality checks. Instead, we want to run code quality checks all the time, but not the lengthy test.

To add a quality check lifecycle task, we introduce an additional lifecycle task called `qualityCheck` and a plugin called `spotbugs`.

To add a lifecycle task, use `tasks.register()`. The only thing you need to provide is a name. Put this task in our group and wire the actionable tasks that belong to this new lifecycle task using the `dependsOn()` method:

app/build.gradle.kts

    plugins {
        id("com.github.spotbugs") version "6.0.7"           // spotbugs plugin
    }

    tasks.register("qualityCheck") {                        // qualityCheck task
        group = myBuildGroup                                // group
        description = "Runs checks (excluding tests)."      // description
        dependsOn(tasks.classes, tasks.spotbugsMain)        // dependencies
        dependsOn(tasks.testClasses, tasks.spotbugsTest)    // dependencies
    }

app/build.gradle

    plugins {
        id 'com.github.spotbugs' version '6.0.7'            // spotbugs plugin
    }

    tasks.register('qualityCheck') {                        // qualityCheck task
        group = myBuildGroup                                // group
        description = 'Runs checks (excluding tests).'      // description
        dependsOn tasks.classes, tasks.spotbugsMain         // dependencies
        dependsOn tasks.testClasses, tasks.spotbugsTest     // dependencies
    }

Note that you don’t need to list all the tasks that Gradle will execute. Just specify the targets you want to collect here. Gradle will determine which other tasks it needs to call to reach these goals.

In the example, we add the `classes` task, a lifecycle task to compile all our production code, and the `spotbugsMain` task, which checks our production code.

We also add a description that will show up in the task list that helps distinguish the two check tasks better.

Now, if run './gradlew :app:tasks', we can see that our new `qualityCheck` lifecycle task is available:

    $ ./gradlew :app:tasks

    > Task :app:tasks

    ------------------------------------------------------------
    Tasks runnable from project ':app'
    ------------------------------------------------------------

    My app build tasks
    ------------------
    build - Assembles and tests this project.
    check - Runs checks (including tests).
    qualityCheck - Runs checks (excluding tests).
    run - Runs this project as a JVM application
    tasksAll - Show additional tasks.

If we run it, we can see that it runs checkstyle but not the tests:

    $ ./gradlew :app:qualityCheck

    > Task :buildSrc:checkKotlinGradlePluginConfigurationErrors
    > Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
    > Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
    > Task :buildSrc:compilePluginsBlocks UP-TO-DATE
    > Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
    > Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
    > Task :buildSrc:compileKotlin UP-TO-DATE
    > Task :buildSrc:compileJava NO-SOURCE
    > Task :buildSrc:compileGroovy NO-SOURCE
    > Task :buildSrc:pluginDescriptors UP-TO-DATE
    > Task :buildSrc:processResources UP-TO-DATE
    > Task :buildSrc:classes UP-TO-DATE
    > Task :buildSrc:jar UP-TO-DATE
    > Task :app:processResources NO-SOURCE
    > Task :app:processTestResources NO-SOURCE
    > Task :list:compileJava UP-TO-DATE
    > Task :utilities:compileJava UP-TO-DATE
    > Task :app:compileJava
    > Task :app:classes
    > Task :app:compileTestJava
    > Task :app:testClasses
    > Task :app:spotbugsTest
    > Task :app:spotbugsMain
    > Task :app:qualityCheck

    BUILD SUCCESSFUL in 1s
    16 actionable tasks: 5 executed, 11 up-to-date

So far, we have looked at tasks in individual subprojects, which is useful for local development when you work on code in one subproject.

With this setup, developers only need to know that they can call Gradle with `:subproject-name:tasks` to see which tasks are available and useful for them.

## Global lifecycle tasks

Another place to invoke lifecycle tasks is within the root build; this is especially useful for Continuous Integration (CI).

Gradle tasks play a crucial role in CI or CD systems, where activities like compiling all code, running tests, or building and packaging the complete application are typical. To facilitate this, you can include lifecycle tasks that span multiple subprojects.

| Gradle has been around for a long time, and you will frequently observe build files in the root directory serving various purposes. In older Gradle versions, many tasks were defined within the root Gradle build file, resulting in various issues. Therefore, exercise caution when determining the content of this file.

One of the few elements that should be placed in the root build file is global lifecycle tasks.

Let’s continue using the Gradle `init` Java application multi-project as an example.

This time, we’re incorporating a build script in the root project. We’ll establish two groups for our global lifecycle tasks: one for tasks relevant to local development, such as running all checks, and another exclusively for our CI system.

Once again, we narrowed down the tasks listed to our specific groups:

build.gradle.kts

    val globalBuildGroup = "My global build"
    val ciBuildGroup = "My CI build"

    tasks.named<TaskReportTask>("tasks") {
        displayGroups = listOf<String>(globalBuildGroup, ciBuildGroup)
    }

build.gradle

    def globalBuildGroup = "My global build"
    def ciBuildGroup = "My CI build"

    tasks.named(TaskReportTask, "tasks") {
        displayGroups = [globalBuildGroup, ciBuildGroup]
    }

You could hide the CI tasks if you wanted to by updating `displayGroups`.

Currently, the root project exposes no tasks:

    $ ./gradlew :tasks

    > Task :tasks

    ------------------------------------------------------------
    Tasks runnable from root project 'gradle-project'
    ------------------------------------------------------------

    No tasks

| In this file, we don’t apply a plugin!

Let’s add a `qualityCheckApp` task to execute all code quality checks in the `app` subproject. Similarly, for CI purposes, we implement a `checkAll` task that runs all tests:

build.gradle.kts

    tasks.register("qualityCheckApp") {
        group = globalBuildGroup
        description = "Runs checks on app (globally)"
        dependsOn(":app:qualityCheck" )
    }

    tasks.register("checkAll") {
        group = ciBuildGroup
        description = "Runs checks for all projects (CI)"
        dependsOn(subprojects.map { ":${it.name}:check" })
        dependsOn(gradle.includedBuilds.map { it.task(":checkAll") })
    }

build.gradle

    tasks.register("qualityCheckApp") {
        group = globalBuildGroup
        description = "Runs checks on app (globally)"
        dependsOn(":app:qualityCheck")
    }

    tasks.register("checkAll") {
        group = ciBuildGroup
        description = "Runs checks for all projects (CI)"
        dependsOn subprojects.collect { ":${it.name}:check" }
        dependsOn gradle.includedBuilds.collect { it.task(":checkAll") }
    }

So we can now ask Gradle to show us the tasks for the root project and, by default, it will only show us the `qualityCheckAll` task (and optionally the `checkAll` task depending on the value of `displayGroups`).

It should be clear what a user should run locally:

    $ ./gradlew :tasks

    > Task :tasks

    ------------------------------------------------------------
    Tasks runnable from root project 'gradle-project'
    ------------------------------------------------------------

    My CI build tasks
    -----------------
    checkAll - Runs checks for all projects (CI)

    My global build tasks
    ---------------------
    qualityCheckApp - Runs checks on app (globally)

If we run the `:checkAll` task, we see that it compiles all the code and runs the code quality checks (including `spotbug`):

    $ ./gradlew :checkAll

    > Task :buildSrc:checkKotlinGradlePluginConfigurationErrors
    > Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
    > Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
    > Task :buildSrc:compilePluginsBlocks UP-TO-DATE
    > Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
    > Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
    > Task :buildSrc:compileKotlin UP-TO-DATE
    > Task :buildSrc:compileJava NO-SOURCE
    > Task :buildSrc:compileGroovy NO-SOURCE
    > Task :buildSrc:pluginDescriptors UP-TO-DATE
    > Task :buildSrc:processResources UP-TO-DATE
    > Task :buildSrc:classes UP-TO-DATE
    > Task :buildSrc:jar UP-TO-DATE
    > Task :utilities:processResources NO-SOURCE
    > Task :app:processResources NO-SOURCE
    > Task :utilities:processTestResources NO-SOURCE
    > Task :app:processTestResources NO-SOURCE
    > Task :list:compileJava
    > Task :list:processResources NO-SOURCE
    > Task :list:classes
    > Task :list:jar
    > Task :utilities:compileJava
    > Task :utilities:classes
    > Task :utilities:jar
    > Task :utilities:compileTestJava NO-SOURCE
    > Task :utilities:testClasses UP-TO-DATE
    > Task :utilities:test NO-SOURCE
    > Task :utilities:check UP-TO-DATE
    > Task :list:compileTestJava
    > Task :list:processTestResources NO-SOURCE
    > Task :list:testClasses
    > Task :app:compileJava
    > Task :app:classes
    > Task :app:compileTestJava
    > Task :app:testClasses
    > Task :list:test
    > Task :list:check
    > Task :app:test
    > Task :app:spotbugsTest
    > Task :app:spotbugsMain
    > Task :app:check
    > Task :checkAll

    BUILD SUCCESSFUL in 1s
    21 actionable tasks: 12 executed, 9 up-to-date