skip to content

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

level: middleimportance: must knowfreq 60%

answer

  1. tasks.register returns TaskProvider
  2. register = lazy, create = eager
  3. realized only when needed / in graph
  4. task configuration avoidance API
  5. named + configureEach stay lazy

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.

solid answer

~40 s

You add a custom task to the project via the task container: `tasks.register("name", MyTask::class) { /* configure */ }`. The configuration lambda lets you set the task's properties. Prefer `register` over the older `create` because `register` returns a `TaskProvider` and is **lazy** — the task is not realized (instantiated and configured) until something in the build actually requires it (it appears in the task graph, is looked up by name, or `get()` is called). `create` eagerly realizes the task during configuration of *every* build invocation, even when it will never run. With large builds this laziness is a meaningful configuration-time performance win, and it is the foundation of Gradle's task-avoidance / configuration-avoidance APIs. Use the returned `TaskProvider` for wiring without forcing realization.

code

kotlin · 7 lines
kotlin
// Lazy registration
val greet = tasks.register("greet", GreetTask::class) {
    message.set("hi")
}

// Wire without forcing realization
tasks.named("build") { dependsOn(greet) }

go deeper

for a junior

Know the tasks.register(name, Type::class) { } call and that it adds the task.

for a middle

Explain lazy vs eager, the TaskProvider return type, and that register avoids configuring unneeded tasks.

for a senior

Detail realization triggers and how named/configureEach preserve laziness; tie it to configuration performance.

for a principal

Set a team standard: register-only, provider-based wiring, ban eager create/getByName in shared plugins to protect configuration time at scale.

## Registering a task type Gradle exposes a `TaskContainer` as `project.tasks`. To add an instance of a custom type you call: ```kotlin val greet = tasks.register("greet", GreetTask::class) { message.set("hello") } ``` The trailing lambda is the **configuration action** — it runs against the task instance and is where you set inputs/outputs and dependencies. In Groovy DSL: `tasks.register('greet', GreetTask) { ... }`. ## `register` vs `create` - `tasks.create(...)` — the **eager** legacy API. It instantiates and configures the task immediately, during the configuration phase, on every build run. - `tasks.register(...)` — the **lazy** API (the *task configuration avoidance* API, GA since Gradle 4.9). It returns a `TaskProvider<T>` and defers instantiation+configuration until the task is *realized* — i.e. it is needed to build the task graph, looked up via `tasks.named(...)`/`get()`, or its provider is queried. ## Why laziness matters In a multi-module build with hundreds of tasks, most tasks are irrelevant to any given invocation (`./gradlew compileKotlin` does not need the `publish` task). Eager `create` configures all of them anyway, inflating configuration time. `register` skips configuration for tasks that never enter the graph. This is one of the biggest levers for fast configuration and complements the configuration cache. ## Realization triggers A registered task gets realized when: - it (or a task that depends on it) is in the requested task graph; - you call `tasks.named("greet").get()` or `tasks.getByName("greet")`; - you iterate the container eagerly (`tasks.forEach { }` instead of `tasks.configureEach { }`). Use `tasks.named("greet")` (returns a provider) and `configureEach` to stay lazy. ## TaskProvider for wiring ```kotlin val greet = tasks.register("greet", GreetTask::class) tasks.register("all") { dependsOn(greet) } ``` Wiring with the provider does not force `greet` to realize unless `all` actually runs.

  • What does tasks.register return and how is it different from tasks.create?
    register returns a TaskProvider<T> and defers creation/configuration; create returns the realized task instance and configures it immediately during every configuration phase.
  • Name one thing that forces a registered task to be realized.
    Calling get() on its provider, tasks.getByName(name), eager iteration of the container, or the task entering the requested task graph.
  • How does register interact with the configuration cache?
    Laziness reduces configuration-phase work and avoids realizing unneeded tasks, which keeps the configuration model small and cacheable; both target faster configuration.

saying these in an interview costs you the question

  • Saying register and create behave identically.
  • Claiming register configures the task immediately.
  • Using tasks.getByName/get() everywhere, which defeats the laziness register provides.

context