What is DefaultTask and how do you create a custom task type by subclassing it?
answer
- Task interface vs DefaultTask base class
- @TaskAction = execution-phase entry point
- abstract class -> Gradle generates impl
- tasks.register(name, Type::class)
- constructor runs at config time, action at exec time
basics
~10 sDefaultTask 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 linesabstract 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
Know that DefaultTask is the base class and @TaskAction marks the method that does the work when the task runs.
Explain config-time vs exec-time separation, why the class is abstract, and how registration realizes the task.
Discuss managed properties, service injection via abstract getters, and laziness of register vs create.
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.