What is the difference between tasks.register and tasks.create in Gradle, and which should you prefer?
answer
- create = eager, register = lazy
- register returns TaskProvider<T>
- config block runs at configuration vs on-demand
- configuration avoidance
- by registering / tasks.register('x')
basics
~10 stasks.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// 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
Know the one-liner: create is eager (configures now), register is lazy (configures on demand), prefer register. Recognize the Groovy/Kotlin forms.
Explain TaskProvider vs Task, when realization is triggered, and tie register to configuration-phase performance in multi-project builds.
Discuss how mixing eager APIs forces realization, the by registering delegate, and migrating a legacy create-heavy build to register.
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).