How do you register a custom DefaultTask subclass, and why prefer tasks.register over tasks.create?
answer
- tasks.register returns TaskProvider
- register = lazy, create = eager
- realized only when needed / in graph
- task configuration avoidance API
- named + configureEach stay lazy
basics
~10 sRegister 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 sYou 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// Lazy registration
val greet = tasks.register("greet", GreetTask::class) {
message.set("hi")
}
// Wire without forcing realization
tasks.named("build") { dependsOn(greet) }go deeper
Know the tasks.register(name, Type::class) { } call and that it adds the task.
Explain lazy vs eager, the TaskProvider return type, and that register avoids configuring unneeded tasks.
Detail realization triggers and how named/configureEach preserve laziness; tie it to configuration performance.
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.