Lifecycle Tasks
Lifecycle tasks are tasks that do not do work themselves. These tasks have no actions. They serve as targets for the build.

Lifecycle tasks represent various concepts:
-
a work-flow step (e.g., run all checks with
check
) -
a buildable thing (e.g., create a debug 32-bit executable for native components with
debug32MainExecutable
) -
a convenience task to execute many of the same logical tasks (e.g., run all compilation tasks with
compileAll
)
The Gradle base
plugin defines several lifecycle tasks, including build
, assemble
, and check
.
A well-organized setup of lifecycle tasks enhances the accessibility of your build for new users and simplifies integration with CI.
Task group and description
Let’s consider a basic Java application as an example.
The build contains a subproject called app
.
Let’s list the available tasks in app
at the moment:
$ ./gradlew :app:tasks
> Task :app:tasks
------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------
Application tasks
-----------------
run - Runs this project as a JVM application
Build tasks
-----------
assemble - Assembles the outputs of this project.
When listing available tasks in the app
subproject, we see that tasks are grouped.
Here, the :run
task is part of the Application
group with the description Runs this project as a JVM application
.
Private and hidden tasks
Gradle doesn’t support marking a task as private.
However, tasks will only show up when running :tasks
if task.group
is set or no other task depends on it.
For instance, the following task will not appear when running ./gradlew :app:tasks
because it does not have a group; it is called a hidden task:
tasks.register("helloTask") {
println("Hello")
}
tasks.register("helloTask") {
println("Hello")
}
Although helloTask
is not listed, it can still be executed by Gradle:
$ ./gradlew :app:tasks
> Task :app:tasks
------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------
Application tasks
-----------------
run - Runs this project as a JVM application
Build tasks
-----------
assemble - Assembles the outputs of this project.
Let’s add a group to the same task:
tasks.register("helloTask") {
group = "Other"
println("Hello")
}
tasks.register("helloTask") {
group = "Other"
println("Hello")
}
Now that the group is added, the task is visible:
$ ./gradlew :app:tasks
> Task :app:tasks
------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------
Application tasks
-----------------
run - Runs this project as a JVM application
Build tasks
-----------
assemble - Assembles the outputs of this project.
Other tasks
-----------
helloTask
In contrast, ./gradlew tasks --all
will show all tasks; hidden and visible tasks are listed.
Grouping tasks
If you want to customize which tasks are shown to users when listed, you can group tasks and set the visibility of each group.
Remember, even if you hide tasks, they are still available, and Gradle can still run them. |
Let’s start with an example built by Gradle init
for a Java application with multiple subprojects.
The project structure is as follows:
gradle-project
├── app
│ ├── build.gradle.kts
│ └── src // some java code
│ └── ...
├── utilities
│ ├── build.gradle.kts
│ └── src // some java code
│ └── ...
├── list
│ ├── build.gradle.kts
│ └── src // some java code
│ └── ...
├── buildSrc
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ └── src // common build logic
│ └── ...
├── settings.gradle.kts
├── gradle
├── gradlew
└── gradlew.bat
gradle-project
├── app
│ ├── build.gradle
│ └── src // some java code
│ └── ...
├── utilities
│ ├── build.gradle
│ └── src // some java code
│ └── ...
├── list
│ ├── build.gradle
│ └── src // some java code
│ └── ...
├── buildSrc
│ ├── build.gradle
│ ├── settings.gradle
│ └── src // common build logic
│ └── ...
├── settings.gradle
├── gradle
├── gradlew
└── gradlew.bat
Run app:tasks
to see available tasks in the app
subproject:
$ ./gradlew :app:tasks
> Task :app:tasks
------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------
Application tasks
-----------------
run - Runs this project as a JVM application
Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the classes of the 'main' feature.
testClasses - Assembles test classes.
Distribution tasks
------------------
assembleDist - Assembles the main distributions
distTar - Bundles the project as a distribution.
distZip - Bundles the project as a distribution.
installDist - Installs the project as a distribution as-is.
Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the 'main' feature.
Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in project ':app'.
dependencies - Displays all dependencies declared in project ':app'.
dependencyInsight - Displays the insight into a specific dependency in project ':app'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
kotlinDslAccessorsReport - Prints the Kotlin code for accessing the currently available project extensions and conventions.
outgoingVariants - Displays the outgoing variants of project ':app'.
projects - Displays the sub-projects of project ':app'.
properties - Displays the properties of project ':app'.
resolvableConfigurations - Displays the configurations that can be resolved in project ':app'.
tasks - Displays the tasks runnable from project ':app'.
Verification tasks
------------------
check - Runs all checks.
test - Runs the test suite.
If we look at the list of tasks available, even for a standard Java project, it’s extensive. Many of these tasks are rarely required directly by developers using the build.
We can configure the :tasks
task and limit the tasks shown to a certain group.
Let’s create our own group so that all tasks are hidden by default by updating the app
build script:
val myBuildGroup = "my app build" // Create a group name
tasks.register<TaskReportTask>("tasksAll") { // Register the tasksAll task
group = myBuildGroup
description = "Show additional tasks."
setShowDetail(true)
}
tasks.named<TaskReportTask>("tasks") { // Move all existing tasks to the group
displayGroup = myBuildGroup
}
def myBuildGroup = "my app build" // Create a group name
tasks.register(TaskReportTask, "tasksAll") { // Register the tasksAll task
group = myBuildGroup
description = "Show additional tasks."
setShowDetail(true)
}
tasks.named(TaskReportTask, "tasks") { // Move all existing tasks to the group
displayGroup = myBuildGroup
}
Now, when we list tasks available in app
, the list is shorter:
$ ./gradlew :app:tasks
> Task :app:tasks
------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------
My app build tasks
------------------
tasksAll - Show additional tasks.
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 expose three additional tasks in our example, the build
task, the check
task, and the run
task by adding the following lines to the app
build script:
tasks.build {
group = myBuildGroup
}
tasks.check {
group = myBuildGroup
description = "Runs checks (including tests)."
}
tasks.named("run") {
group = myBuildGroup
}
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:
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
}
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:
val globalBuildGroup = "My global build"
val ciBuildGroup = "My CI build"
tasks.named<TaskReportTask>("tasks") {
displayGroups = listOf<String>(globalBuildGroup, ciBuildGroup)
}
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:
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") })
}
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
Task name abbreviation
When specifying tasks on the command line, providing the complete task name is unnecessary. You can provide enough of the task name to identify the task uniquely.
For example, instead of running ./gradlew :checkAll
, we can run ./gradlew :chAl
:
$ ./gradlew :chAl
> Task :buildSrc:checkKotlinGradlePluginConfigurationErrors
> Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
...
> Task :app:test UP-TO-DATE
> Task :app:check UP-TO-DATE
> Task :checkAll UP-TO-DATE
BUILD SUCCESSFUL in 412ms
21 actionable tasks: 1 executed, 20 up-to-date
The same applies to project names.
You can execute the :check
task in the utitlities
subproject with the gradle uti:che
command.
You can use camel case patterns for more complex abbreviations.
Calling tasks without the colon :
in front is not advised.
If you do so, Gradle will attempt to find matching tasks in all the subprojects, and you won’t precisely know which tasks will be invoked.
For instance, Gradle will skip projects where the task is unavailable.
It is recommended that you adhere to the ':' notation, ensuring the appropriate lifecycle tasks are available so that everyone is aware of the intended usage in your build.
Exclude tasks from execution
You can exclude a task from execution using the -x
or --exclude-task
command-line option and provide the task’s name to exclude.
For instance, you can run the check
task but exclude the test
task from running.
This approach can lead to unexpected outcomes, particularly if you exclude an actionable task that produces results needed by other tasks.
Instead of relying on the -x
parameter, defining a suitable lifecycle task for the desired action is recommended.
Using -x
is a practice that should be avoided, although still commonly observed.