A task represents some independent unit of work that a build performs, such as compiling classes, creating a JAR, generating Javadoc, or publishing archives to a repository.

writing tasks 1

Before reading this chapter, it’s recommended that you first read the Learning The Basics and complete the Tutorial.

Listing tasks

All available tasks in your project come from Gradle plugins and build scripts.

You can list all the available tasks in a project by running the following command in the terminal:

$ ./gradlew tasks

Let’s take a very basic Gradle project as an example. The project has the following structure:

gradle-project
├── app
│   ├── build.gradle.kts    // empty file - no build logic
│   └── ...                 // some java code
├── settings.gradle.kts     // includes app subproject
├── gradle
│   └── ...
├── gradlew
└── gradlew.bat
gradle-project
├── app
│   ├── build.gradle    // empty file - no build logic
│   └── ...             // some java code
├── settings.gradle     // includes app subproject
├── gradle
│   └── ...
├── gradlew
└── gradlew.bat

The settings file contains the following:

settings.gradle.kts
rootProject.name = "gradle-project"
include("app")
settings.gradle
rootProject.name = 'gradle-project'
include('app')

Currently, the app subproject’s build file is empty.

To see the tasks available in the app subproject, run ./gradlew :app:tasks:

$ ./gradlew :app:tasks

> Task :app:tasks

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

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'.

We observe that only a small number of help tasks are available at the moment. This is because the core of Gradle only provides tasks that analyze your build. Other tasks, such as the those that build your project or compile your code, are added by plugins.

Let’s explore this by adding the Gradle core base plugin to the app build script:

app/build.gradle.kts
plugins {
    id("base")
}
app/build.gradle
plugins {
    id('base')
}

The base plugin adds central lifecycle tasks. Now when we run ./gradlew app:tasks, we can see the assemble and build tasks are available:

$ ./gradlew :app:tasks

> Task :app:tasks

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

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
clean - Deletes the build directory.

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.
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.

Task outcomes

When Gradle executes a task, it labels the task with outcomes via the console.

author tasks 1

These labels are based on whether a task has actions to execute and if Gradle executed them. Actions include, but are not limited to, compiling code, zipping files, and publishing archives.

(no label) or EXECUTED

Task executed its actions.

  • Task has actions and Gradle executed them.

  • Task has no actions and some dependencies, and Gradle executed one or more of the dependencies. See also Lifecycle Tasks.

UP-TO-DATE

Task’s outputs did not change.

  • Task has outputs and inputs but they have not changed. See Incremental Build.

  • Task has actions, but the task tells Gradle it did not change its outputs.

  • Task has no actions and some dependencies, but all the dependencies are UP-TO-DATE, SKIPPED or FROM-CACHE. See Lifecycle Tasks.

  • Task has no actions and no dependencies.

FROM-CACHE

Task’s outputs could be found from a previous execution.

  • Task has outputs restored from the build cache. See Build Cache.

SKIPPED

Task did not execute its actions.

NO-SOURCE

Task did not need to execute its actions.

  • Task has inputs and outputs, but no sources (i.e., inputs were not found).

Task group and description

Task groups and descriptions are used to organize and describe tasks.

Groups

Task groups are used to categorize tasks. When you run ./gradlew tasks, tasks are listed under their respective groups, making it easier to understand their purpose and relationship to other tasks. Groups are set using the group property.

Descriptions

Descriptions provide a brief explanation of what a task does. When you run ./gradlew tasks, the descriptions are shown next to each task, helping you understand its purpose and how to use it. Descriptions are set using the description property.

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.

Here, the :run task is part of the Application group with the description Runs this project as a JVM application. In code, it would look something like this:

app/build.gradle.kts
tasks.register("run") {
    group = "Application"
    description = "Runs this project as a JVM application."
}
app/build.gradle
tasks.register("run") {
    group = "Application"
    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:

app/build.gradle.kts
tasks.register("helloTask") {
    println("Hello")
}
app/build.gradle
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:

app/build.gradle.kts
tasks.register("helloTask") {
    group = "Other"
    description = "Hello task"
    println("Hello")
}
app/build.gradle
tasks.register("helloTask") {
    group = "Other"
    description = "Hello task"
    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 - Hello task

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:

app/build.gradle.kts
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
}
app/build.gradle
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.

Task categories

Gradle distinguishes between two categories of tasks:

  1. Lifecycle tasks

  2. Actionable tasks

Lifecycle tasks define targets you can call, such as :build your project. Lifecycle tasks do not provide Gradle with actions. They must be wired to actionable tasks. The base Gradle plugin only adds lifecycle tasks.

Actionable tasks define actions for Gradle to take, such as :compileJava, which compiles the Java code of your project. Actions include creating JARs, zipping files, publishing archives, and much more. Plugins like the java-library plugin adds actionable tasks.

Let’s update the build script of the previous example, which is currently an empty file so that our app subproject is a Java library:

app/build.gradle.kts
plugins {
    id("java-library")
}
app/build.gradle
plugins {
    id('java-library')
}

Once again, we list the available tasks to see what new tasks are available:

$ ./gradlew :app:tasks

> Task :app:tasks

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

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.

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.
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.

We see that many new tasks are available such as jar and testClasses.

Additionally, the java-library plugin has wired actionable tasks to lifecycle tasks. If we call the :build task, we can see several tasks have been executed, including the :app:compileJava task.

$./gradlew :app:build

> Task :app:compileJava
> Task :app:processResources NO-SOURCE
> Task :app:classes
> Task :app:jar
> Task :app:assemble
> Task :app:compileTestJava
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses
> Task :app:test
> Task :app:check
> Task :app:build

The actionable :compileJava task is wired to the lifecycle :build task.

Incremental tasks

A key feature of Gradle tasks is their incremental nature.

Gradle can reuse results from prior builds. Therefore, if we’ve built our project before and made only minor changes, rerunning :build will not require Gradle to perform extensive work.

For example, if we modify only the test code in our project, leaving the production code unchanged, executing the build will solely recompile the test code. Gradle marks the tasks for the production code as UP-TO-DATE, indicating that it remains unchanged since the last successful build:

$./gradlew :app:build

lkassovic@MacBook-Pro temp1 % ./gradlew :app:build
> Task :app:compileJava UP-TO-DATE
> Task :app:processResources NO-SOURCE
> Task :app:classes UP-TO-DATE
> Task :app:jar UP-TO-DATE
> Task :app:assemble UP-TO-DATE
> Task :app:compileTestJava
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses
> Task :app:test
> Task :app:check UP-TO-DATE
> Task :app:build UP-TO-DATE

Caching tasks

Gradle can reuse results from past builds using the build cache.

To enable this feature, activate the build cache by using the --build-cache command line parameter or by setting org.gradle.caching=true in your gradle.properties file.

This optimization has the potential to accelerate your builds significantly:

$./gradlew :app:clean :app:build --build-cache

> Task :app:compileJava FROM-CACHE
> Task :app:processResources NO-SOURCE
> Task :app:classes UP-TO-DATE
> Task :app:jar
> Task :app:assemble
> Task :app:compileTestJava FROM-CACHE
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses UP-TO-DATE
> Task :app:test FROM-CACHE
> Task :app:check UP-TO-DATE
> Task :app:build

When Gradle can fetch outputs of a task from the cache, it labels the task with FROM-CACHE.

The build cache is handy if you switch between branches regularly. Gradle supports both local and remote build caches.

Developing tasks

When developing Gradle tasks, you have two choices:

  1. Use an existing Gradle task type such as Zip, Copy, or Delete

  2. Create your own Gradle task type such as MyResolveTask or CustomTaskUsingToolchains.

Task types are simply subclasses of the Gradle Task class.

With Gradle tasks, there are three states to consider:

  1. Registering a task - using a task (implemented by you or provided by Gradle) in your build logic.

  2. Configuring a task - defining inputs and outputs for a registered task.

  3. Implementing a task - creating a custom task class (i.e., custom class type).

Registration is commonly done with the register() method.
Configuring a task is commonly done with the named() method.
Implementing a task is commonly done by extending Gradle’s DefaultTask class:

tasks.register<Copy>("myCopy")                              (1)

tasks.named<Copy>("myCopy") {                               (2)
    from("resources")
    into("target")
    include("**/*.txt", "**/*.xml", "**/*.properties")
}

abstract class MyCopyTask : DefaultTask() {                 (3)
    @TaskAction
    fun copyFiles() {
        val sourceDir = File("sourceDir")
        val destinationDir = File("destinationDir")
        sourceDir.listFiles()?.forEach { file ->
            if (file.isFile && file.extension == "txt") {
                file.copyTo(File(destinationDir, file.name))
            }
        }
    }
}
1 Register the myCopy task of type Copy to let Gradle know we intend to use it in our build logic.
2 Configure the registered myCopy task with the inputs and outputs it needs according to its API.
3 Implement a custom task type called MyCopyTask which extends DefaultTask and defines the copyFiles task action.
tasks.register(Copy, "myCopy")                              (1)

tasks.named(Copy, "myCopy") {                               (2)
    from "resources"
    into "target"
    include "**/*.txt", "**/*.xml", "**/*.properties"
}

abstract class MyCopyTask extends DefaultTask {             (3)
    @TaskAction
    void copyFiles() {
        fileTree('sourceDir').matching {
            include '**/*.txt'
        }.forEach { file ->
            file.copyTo(file.path.replace('sourceDir', 'destinationDir'))
        }
    }
}
1 Register the myCopy task of type Copy to let Gradle know we intend to use it in our build logic.
2 Configure the registered myCopy task with the inputs and outputs it needs according to its API.
3 Implement a custom task type called MyCopyTask which extends DefaultTask and defines the copyFiles task action.

1. Registering tasks

You define actions for Gradle to take by registering tasks in build scripts or plugins.

Tasks are defined using strings for task names:

build.gradle.kts
tasks.register("hello") {
    doLast {
        println("hello")
    }
}
build.gradle
tasks.register('hello') {
    doLast {
        println 'hello'
    }
}

In the example above, the task is added to the TasksCollection using the register() method in TaskContainer.

2. Configuring tasks

Gradle tasks must be configured to complete their action(s) successfully. If a task needs to ZIP a file, it must be configured with the file name and location. You can refer to the API for the Gradle Zip task to learn how to configure it appropriately.

Let’s look at the Copy task provided by Gradle as an example. We first register a task called myCopy of type Copy in the build script:

build.gradle.kts
tasks.register<Copy>("myCopy")
build.gradle
tasks.register('myCopy', Copy)

This registers a copy task with no default behavior. Since the task is of type Copy, a Gradle supported task type, it can be configured using its API.

The following examples show several ways to achieve the same configuration:

1. Using the named() method:

Use named() to configure an existing task registered elsewhere:

build.gradle.kts
tasks.named<Copy>("myCopy") {
    from("resources")
    into("target")
    include("**/*.txt", "**/*.xml", "**/*.properties")
}
build.gradle
tasks.named('myCopy') {
    from 'resources'
    into 'target'
    include('**/*.txt', '**/*.xml', '**/*.properties')
}

2. Using a configuration block:

Use a block to configure the task immediately upon registering it:

build.gradle.kts
tasks.register<Copy>("copy") {
   from("resources")
   into("target")
   include("**/*.txt", "**/*.xml", "**/*.properties")
}
build.gradle
tasks.register('copy', Copy) {
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}

3. Name method as call:

A popular option that is only supported in Groovy is the shorthand notation:

copy {
    from("resources")
    into("target")
    include("**/*.txt", "**/*.xml", "**/*.properties")
}
This option breaks task configuration avoidance and is not recommended!

Regardless of the method chosen, the task is configured with the name of the files to be copied and the location of the files.

3. Implementing tasks

Gradle provides many task types including Delete, Javadoc, Copy, Exec, Tar, and Pmd. You can implement a custom task type if Gradle does not provide a task type that meets your build logic needs.

To create a custom task class, you extend DefaultTask and make the extending class abstract:

app/build.gradle.kts
abstract class MyCopyTask : DefaultTask() {

}
app/build.gradle
abstract class MyCopyTask extends DefaultTask {

}