skip to content

register (Lazy) vs create (Eager)

tasks.register returning a lazy TaskProvider versus tasks.create instantiating immediately, and when the configuration block actually runs. One of the most reliable Gradle interview questions, because the answer explains why large builds get slow before they do anything.

on this pageshow

questions

5

What is the difference between tasks.register and tasks.create in Gradle, and which should you prefer?

level: juniorimportance: must knowfreq 70%

answer

  1. create = eager, register = lazy
  2. register returns TaskProvider<T>
  3. config block runs at configuration vs on-demand
  4. configuration avoidance
  5. by registering / tasks.register('x')

basics

~10 s

tasks.create builds the task eagerly and runs its configuration immediately. tasks.register is lazy: it only registers the task and runs configuration if/when the task is actually needed. Prefer register.

solid answer

~40 s

`tasks.create("x")` is eager — Gradle instantiates the task object and runs its configuration block immediately during the configuration phase, every build, whether or not the task runs. `tasks.register("x")` is lazy — it records that a task named `x` exists and returns a `TaskProvider<T>`; the task is only realized (instantiated and configured) when something actually queries or runs it. Preferring `register` is part of Gradle's configuration-avoidance approach: in a large multi-project build, skipping the creation/configuration of tasks you never run cuts configuration-phase time significantly. `create` returns the `Task` directly, `register` returns a `TaskProvider<T>` you resolve later with `.get()` or wire lazily. Modern Gradle plugins use `register` everywhere; `create` survives mainly for legacy code and rare cases needing the concrete instance up front.

code

kotlin · 8 lines
kotlin
// eager: runs now, every build
tasks.create("eager") { println("configured eagerly") }

// lazy: returns a TaskProvider, configures only if realized
val lazy = tasks.register("lazy") { println("configured lazily") }

// Kotlin DSL sugar, also lazy
val report by registering { description = "runs on demand" }

go deeper

for a junior

Know the one-liner: create is eager (configures now), register is lazy (configures on demand), prefer register. Recognize the Groovy/Kotlin forms.

for a middle

Explain TaskProvider vs Task, when realization is triggered, and tie register to configuration-phase performance in multi-project builds.

for a senior

Discuss how mixing eager APIs forces realization, the by registering delegate, and migrating a legacy create-heavy build to register.

for a principal

Frame configuration avoidance as a build-scalability concern across a large monorepo; set org conventions and lint/forbid eager APIs in shared plugins.

## The two phases that matter A Gradle build runs in phases: **configuration** (evaluate every build script, build the task graph) and **execution** (run the tasks the user requested). Anything done eagerly during configuration is paid on *every* invocation — even `gradle help` — regardless of which tasks ultimately run. ## create — eager ```kotlin tasks.create("report") { println("configuring report") // runs NOW, every build } ``` `tasks.create` immediately: 1. instantiates the task object, 2. runs the configuration closure, 3. returns the concrete `Task` instance. So the cost is paid up front, always. ## register — lazy ```kotlin val report = tasks.register("report") { println("configuring report") // runs ONLY if report is realized } ``` `tasks.register` returns a **`TaskProvider<T>`** — a lazy handle. The task is **realized** (instantiated + its configuration block executed) only when something forces it: the task is in the requested execution graph, someone calls `provider.get()`, or `tasks.named(...)`/`withType` touches it. If `report` is never needed in a given build, its configuration block never runs. ## DSL forms - **Kotlin DSL, lazy:** `val report by registering { ... }` (delegated-property sugar over `register`) or `tasks.register("report") { ... }`. - **Groovy DSL, lazy:** `tasks.register('report') { ... }`. - Typed: `tasks.register("zipIt", Zip::class) { ... }`. ## Why prefer register This is the heart of Gradle's **configuration avoidance API**. In a build with hundreds of tasks, eagerly configuring all of them when you only run one is wasted work. `register` lets Gradle skip everything not on the path to what you asked for. Plugins authored after Gradle 4.9 use `register`/`named` throughout, and mixing an eager API (`create`, `getByName`, `all`) anywhere can *force realization* of tasks you wanted to keep lazy. ## Mental model `create` = "make it and configure it now." `register` = "promise it exists; build and configure it only on demand."

  • What type does each method return?
    tasks.create returns the concrete Task instance (e.g. Task or a typed subtype). tasks.register returns a TaskProvider<T>, a lazy handle you resolve with .get() or wire lazily.
  • When exactly does a registered task's configuration block run?
    Only when the task is realized — i.e. it is part of the requested task graph, or someone calls provider.get()/configure()/named/withType in a way that forces it. Otherwise it never runs.

create is cooking every dish on the menu the moment the restaurant opens; register is prepping a dish only when a customer actually orders it.

saying these in an interview costs you the question

  • Saying create and register are interchangeable with no performance difference.
  • Claiming register never runs the configuration block (it does — when the task is realized).
  • Saying register returns a Task (it returns a TaskProvider).

context

open as a page

You used tasks.register to keep a task lazy, but it still gets configured on every build. What kinds of code force a registered task to be realized?

level: middleimportance: must knowfreq 55%

basics

~10 s

Eager APIs force realization: tasks.getByName, tasks.create, tasks.all {}, iterating the task container, or calling .get() on a provider. Any of these realizes lazy tasks, undoing the benefit of register.

open as a page

How do you register a typed task (e.g. a Zip task) lazily in both the Kotlin and Groovy DSL, and what does the 'by registering' syntax do?

level: juniorimportance: should knowfreq 40%

basics

~20 s

Pass the type as a second argument: tasks.register("zipIt", Zip::class) { } in Kotlin, or tasks.register('zipIt', Zip) { } in Groovy. 'by registering' is Kotlin delegate sugar that registers and binds a val to the provider.

open as a page

How do you reference and wire a task declared with tasks.register without realizing it? Show how to use the returned TaskProvider.

level: middleimportance: should knowfreq 45%

basics

~10 s

register returns a TaskProvider. Pass the provider itself to dependsOn or inputs — Gradle resolves it lazily. Use provider.configure { } to add config, and flatMap to read its outputs without calling get().

open as a page

You're migrating an old build that uses tasks.create everywhere to tasks.register. What changes in behavior must you watch for, and why isn't it always a drop-in replacement?

level: seniorimportance: should knowfreq 35%

basics

~20 s

create returns a Task, register returns a TaskProvider — callers expecting a concrete Task break. Code that relied on the task existing/configured immediately (side effects, ordering, getByName) may now run later or not at all unless realized.

open as a page