Creating and Registering Tasks
The work that Gradle can do on a project is defined by one or more tasks.

A task represents some independent unit of work that a build performs. This might be compiling some classes, creating a JAR, generating Javadoc, or publishing some archives to a repository.
When a user runs ./gradlew build
in the command line, Gradle will execute the build
task along with any other tasks it depends on.
Task Types
A task type defines what kind of work a task can do. It’s like a blueprint or class.
Gradle includes many built-in task types, such as Copy
, Jar
, and Test
, and you can also define your own.
By default, a task is of type DefaultTask
.
Let’s start with a simple custom task that prints a message:
tasks.register("hello") {
doLast {
println("Hello world!")
}
}
tasks.register('hello') {
doLast {
println 'Hello world!'
}
}
You just registered a task called hello
of type DefaultTask
and gave it an action using doLast{}
.
A task is created in the build script using the TaskContainer.register()
method, which allows it to be then used in the build logic.
When you run the hello
task in the command-line using ./gradlew hello
, it prints your message:
$ ./gradlew hello
Hello world!
When you register (i.e. create) a task in your build script, you can:
-
Use the default task type (
DefaultTask
) and define the behavior inline. -
Use a built-in task type, like
Copy
, to take advantage of pre-defined behavior. -
Create and use a custom task type if you need reusable behavior across tasks.
This example registers a task called copyTask
which copies \*.war
files from the source
directory to the target
directory using the Copy
built-in task type:
tasks.register<Copy>("copyTask") {
from("source")
into("target")
include("*.war")
}
tasks.register('copyTask', Copy) {
from("source")
into("target")
include("*.war")
}
Built-in Task Types
Gradle provides many built-in task types with common and popular functionality, such as copying or deleting files.
This registers a Gradle task named removeOutput
of type Delete
.
When the task runs, it will delete the file build/outputs/1.txt
relative to the project directory.
tasks.register<Delete>("removeOutput") {
delete(layout.buildDirectory.file("outputs/1.txt"))
}
tasks.register('removeOutput', Delete) {
delete layout.buildDirectory.file("outputs/1.txt")
}
There are many task types developers can take advantage of, including GroovyDoc
, Zip
, Jar
, JacocoReport
, Sign
, or Delete
, which are detailed in the DSL.
Custom Task Types
Gradle tasks are a subclass of Task
.
In the example below, the HelloTask
class, a custom task type, is created by extending DefaultTask
(our default task type):
// Extend the DefaultTask class to create a HelloTask class
abstract class HelloTask : DefaultTask() {
@TaskAction
fun hello() {
println("hello from HelloTask")
}
}
// Register the hello Task with type HelloTask
tasks.register<HelloTask>("hello") {
group = "Custom tasks"
description = "A lovely greeting task."
}
// Extend the DefaultTask class to create a HelloTask class
class HelloTask extends DefaultTask {
@TaskAction
void hello() {
println("hello from HelloTask")
}
}
// Register the hello Task with type HelloTask
tasks.register("hello", HelloTask) {
group = "Custom tasks"
description = "A lovely greeting task."
}
The hello
task is registered with the new type HelloTask
.
Executing our new hello
task results in the following:
$ ./gradlew hello
> Task :app:hello
hello from HelloTask
The Gradle help
task can reveal the specifications of the hello
task:
$ ./gradlew help --task hello
> Task :help
Detailed task information for hello
Path
:app:hello
Type
HelloTask (Build_gradle$HelloTask)
Options
--rerun Causes the task to be re-run even if up-to-date.
Description
A lovely greeting task.
Group
Custom tasks
Task Input and Outputs
For a custom task to do useful work, it typically needs some inputs which it uses to produce outputs.
A task can declare those inputs (files, values) and outputs (files it creates). Ideally, these inputs and outputs leverage Gradle managed types. This helps Gradle skip work when nothing has changed:
abstract class CreateAFileTask : DefaultTask() {
@get:Input
abstract val fileText: Property<String>
@Input
val fileName = "myfile.txt"
@OutputFile
val myFile: File = File(fileName)
@TaskAction
fun action() {
myFile.createNewFile()
myFile.writeText(fileText.get())
}
}
abstract class CreateAFileTask extends DefaultTask {
@Input
abstract Property<String> getFileText()
@Input
final String fileName = "myfile.txt"
@OutputFile
final File myFile = new File(fileName)
@TaskAction
void action() {
myFile.createNewFile()
myFile.text = fileText.get()
}
}
Now Gradle knows what the task needs and what it produces. If nothing changes, the task is skipped.
Task Action
Task actions are the blocks of code that define what the custom task does when it runs.
Every task can have one or more actions, and they’re executed during the execution phase of the Gradle build lifecycle.
A task action is typically added using doLast {}
or doFirst {}
:
tasks.register("hello") {
doLast {
println("Hello world!")
}
}
tasks.register('hello') {
doLast {
println 'Hello world!'
}
}
In this example, the action is println("Hello world!")
.
It will run when the task is executed.
Similarly, in the example below, a custom task type is created called GreetingTask
.
The @TaskAction
annotation marks a method that Gradle should call when the task of this type is executed:
abstract class GreetingTask : DefaultTask() {
@TaskAction
fun greet() {
println("hello from GreetingTask")
}
}
// Create a task using the task type
tasks.register<GreetingTask>("hello")
abstract class GreetingTask extends DefaultTask {
@TaskAction
def greet() {
println 'hello from GreetingTask'
}
}
// Create a task using the task type
tasks.register('hello', GreetingTask)
Task Dependencies
You can declare tasks that depend on other tasks:
tasks.register("hello") {
doLast {
println("Hello world!")
}
}
tasks.register("intro") {
dependsOn("hello")
doLast {
println("I'm Gradle")
}
}
tasks.register('hello') {
doLast {
println 'Hello world!'
}
}
tasks.register('intro') {
dependsOn tasks.hello
doLast {
println "I'm Gradle"
}
}
$ gradle -q intro Hello world! I'm Gradle
The dependency of taskX
to taskY
may be declared before taskY
is defined:
tasks.register("taskX") {
dependsOn("taskY")
doLast {
println("taskX")
}
}
tasks.register("taskY") {
doLast {
println("taskY")
}
}
tasks.register('taskX') {
dependsOn 'taskY'
doLast {
println 'taskX'
}
}
tasks.register('taskY') {
doLast {
println 'taskY'
}
}
$ gradle -q taskX taskY taskX
The hello
task from the previous example is updated to include a dependency:
tasks.register("hello") {
group = "Custom"
description = "A lovely greeting task."
doLast {
println("Hello world!")
}
dependsOn(tasks.assemble)
}
tasks.register('hello') {
group = "Custom"
description = "A lovely greeting task."
doLast {
println("Hello world!")
}
dependsOn(tasks.assemble)
}
The hello
task now depends on the assemble
task, which means that Gradle must execute the assemble
task before it can execute the hello
task:
$ ./gradlew :app:hello
> 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:startScripts UP-TO-DATE
> Task :app:distTar UP-TO-DATE
> Task :app:distZip UP-TO-DATE
> Task :app:assemble UP-TO-DATE
> Task :app:hello
Hello world!
Task Configuration
Once registered, tasks can be accessed via the TaskProvider
API for further configuration.
For instance, you can add behavior to an existing task:
tasks.register("hello") {
doLast {
println("Hello Earth")
}
}
tasks.named("hello") {
doFirst {
println("Hello Venus")
}
}
tasks.named("hello") {
doLast {
println("Hello Mars")
}
}
tasks.named("hello") {
doLast {
println("Hello Jupiter")
}
}
tasks.register('hello') {
doLast {
println 'Hello Earth'
}
}
tasks.named('hello') {
doFirst {
println 'Hello Venus'
}
}
tasks.named('hello') {
doLast {
println 'Hello Mars'
}
}
tasks.named('hello') {
doLast {
println 'Hello Jupiter'
}
}
$ gradle -q hello Hello Venus Hello Earth Hello Mars Hello Jupiter
The calls doFirst and doLast can be executed multiple times.
They add an action to the beginning or the end of the task’s actions list.
When the task executes, the actions in the action list are executed in order.
|
A task is optionally configured in a build script using the TaskCollection.named()
method.
Task Classification
There are two classes of tasks that can be executed:
-
Actionable tasks have some action(s) attached to do work in your build:
compileJava
. -
Lifecycle tasks are tasks with no actions attached:
assemble
,build
.
Typically, a lifecycle tasks depends on many actionable tasks, and is used to execute many tasks at once.
Next Step: Learn about Plugins >>