skip to content

DefaultTask Subclasses & @TaskAction

Subclassing DefaultTask with a single @TaskAction entry point, using abstract classes for managed properties, and registering the type. The starting point for every custom-task answer.

on this pageshow

questions

5

What is DefaultTask and how do you create a custom task type by subclassing it?

level: juniorimportance: must knowfreq 70%

answer

  1. Task interface vs DefaultTask base class
  2. @TaskAction = execution-phase entry point
  3. abstract class -> Gradle generates impl
  4. tasks.register(name, Type::class)
  5. constructor runs at config time, action at exec time

basics

~10 s

DefaultTask is Gradle's base class for tasks. You create a custom task type by subclassing it and adding one method annotated with @TaskAction, which holds the work the task runs when executed.

solid answer

~40 s

`DefaultTask` is the standard base class every custom Gradle task extends. To author a task type you subclass `DefaultTask` and put the task's logic in a single method annotated `@TaskAction`. Gradle invokes that method during the execution phase when the task runs. The class is typically declared `abstract` so Gradle can generate implementations for managed properties and inject services. You then register an instance with `tasks.register("greet", GreetTask::class)`. Properties on the task become inputs/outputs and configuration is done lazily via the registration block. Extending `DefaultTask` (rather than the raw `Task` interface) gives you the conventional implementations of dependency tracking, `dependsOn`, lifecycle, and logging out of the box.

code

kotlin · 13 lines
kotlin
abstract class GreetTask : DefaultTask() {
    @get:Input
    abstract val message: Property<String>

    @TaskAction
    fun run() {
        logger.lifecycle("Greeting: ${message.get()}")
    }
}

tasks.register("greet", GreetTask::class) {
    message.set("hello")
}

go deeper

for a junior

Know that DefaultTask is the base class and @TaskAction marks the method that does the work when the task runs.

for a middle

Explain config-time vs exec-time separation, why the class is abstract, and how registration realizes the task.

for a senior

Discuss managed properties, service injection via abstract getters, and laziness of register vs create.

for a principal

Frame conventions for a shared internal plugin: a base task class hierarchy, consistent action design, and how task authoring choices affect build maintainability across teams.

## What `DefaultTask` is In Gradle, a *task* is the unit of work (compile, copy, test, etc.). `Task` is the interface; `DefaultTask` is the concrete base class Gradle provides that implements all the standard plumbing — dependency tracking, `dependsOn`/`mustRunAfter`, the `Logger`, lifecycle state, and so on. When you author a **custom task type**, you subclass `DefaultTask` rather than implementing `Task` from scratch. ## The `@TaskAction` method A custom task class needs exactly one (or more) method annotated with `@org.gradle.api.tasks.TaskAction`. This method is the **entry point** Gradle calls during the *execution phase* when the task actually runs. Code in the constructor or in property initializers runs at *configuration time*; only the `@TaskAction` body runs at *execution time*. Keeping work inside `@TaskAction` is essential for configuration-time vs. execution-time separation. You *can* declare more than one `@TaskAction` method — Gradle runs them in declaration order — but idiomatic tasks use a single action method. ## Abstract task classes Modern Gradle (5+) encourages declaring task types `abstract`. Gradle generates a concrete subclass at runtime, providing implementations for `abstract` getters of managed properties (e.g. `abstract val message: Property<String>`) and enabling **service injection** through `@Inject` constructor parameters or abstract getters. You never instantiate the task with `new`; Gradle does it for you. ## Registering the type You add an instance to a project with the task container: ```kotlin tasks.register("greet", GreetTask::class) { message.set("hello") } ``` `register` is the lazy API — the task is only realized (configured/created) if something actually needs it. ## Minimal example ```kotlin abstract class GreetTask : DefaultTask() { @get:Input abstract val message: Property<String> @TaskAction fun run() { logger.lifecycle("Greeting: ${message.get()}") } } ``` ## Why subclass `DefaultTask` and not `Task` Implementing `Task` directly means re-implementing all the lifecycle and dependency machinery. `DefaultTask` gives it to you for free, so it is the documented, expected base for every custom task type.

  • What runs at configuration time versus execution time in a custom task?
    The constructor, field initializers and the registration/configuration block run at configuration time; only the @TaskAction method body runs at execution time when the task is actually invoked.
  • Why declare the task class abstract?
    So Gradle can generate the concrete implementation, supplying managed property getters (Property/ListProperty) and injected services declared as abstract getters or @Inject parameters.

saying these in an interview costs you the question

  • Saying the @TaskAction code runs at configuration time.
  • Claiming you should implement the Task interface directly instead of extending DefaultTask.
  • Instantiating the task with a constructor call instead of registering it in the task container.

context

open as a page

In a custom DefaultTask, what is the difference between code placed in the constructor and code placed in the @TaskAction method?

level: middleimportance: must knowfreq 55%

basics

~10 s

Constructor code runs at configuration time (every build, whenever the task is realized). @TaskAction code runs at execution time, only when the task actually runs. Put the real work in @TaskAction, not the constructor.

open as a page

How do you register a custom DefaultTask subclass, and why prefer tasks.register over tasks.create?

level: middleimportance: must knowfreq 60%

basics

~10 s

Register a task type with tasks.register("name", MyTask::class) { ... }. Prefer register over create because register is lazy: the task is only configured if it is actually needed, which speeds up configuration.

open as a page

Why are modern custom task types declared abstract, and how does Gradle provide the missing implementations and services?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Declaring a task abstract lets Gradle generate the concrete subclass at runtime. Gradle supplies implementations for abstract managed-property getters (like Property) and injects services through @Inject constructor params or abstract getters.

open as a page

Can a custom task have multiple @TaskAction methods, and how do task actions and doFirst/doLast relate?

level: middleimportance: nice to knowfreq 30%

basics

~10 s

Yes, a task can have several @TaskAction methods; Gradle runs them in declaration order. doFirst and doLast add extra actions that run before and after the @TaskAction methods on the task's action list.

open as a page