skip to content

In the Gradle Kotlin DSL, what is the difference between tasks.register<Jar>("x") and tasks.create<Jar>("x"), and which should you prefer?

level: juniorimportance: must knowfreq 70%

answer

  1. register = lazy TaskProvider
  2. create = eager realized instance
  3. configuration avoidance
  4. configuration-phase cost
  5. prefer register everywhere

basics

~10 s

register() is lazy — the task is only configured if it ends up in the build. create() is eager — it builds and configures the task immediately. Prefer register().

solid answer

~30 s

`tasks.register<Jar>("myJar")` returns a `TaskProvider<Jar>` and defers both creation and configuration until the task is actually needed (queried or scheduled to run). `tasks.create<Jar>("myJar")` returns the realized `Jar` task instance and runs its configuration block immediately during the configuration phase, regardless of whether the task runs. With large builds, eager creation forces every task object to be instantiated and configured on every invocation — even `./gradlew help` — which is the classic configuration-time hot spot. `register` is part of Gradle's *configuration avoidance* API. Always prefer `register` for new tasks; reserve `create` for legacy plugins that genuinely need the instance up front.

code

kotlin · 10 lines
kotlin
// Lazy — preferred
val zip = tasks.register<Zip>("packageDist") {
    archiveFileName.set("dist.zip")
    from(layout.buildDirectory.dir("output"))
}

// Eager — avoid
val zip2 = tasks.create<Zip>("packageDistEager") {
    archiveFileName.set("dist.zip")
}

go deeper

for a junior

Know register is lazy and preferred, create is eager. State the one-line difference.

for a middle

Explain the configuration phase, TaskProvider, and when the block actually runs.

for a senior

Tie it to configuration-time performance on large multi-project builds and the full configuration-avoidance family (named/withType/configureEach).

for a principal

Set a team convention banning eager APIs, discuss build-scan configuration-time metrics, and migrating legacy plugins off create/getByName.

## The configuration phase problem Every Gradle build runs in phases: **initialization → configuration → execution**. During *configuration*, Gradle evaluates your build scripts to build the task graph. Historically, defining a task with `task myJar(type: Jar)` (Groovy) or `tasks.create<Jar>("myJar")` (Kotlin) *realized* the task immediately: it allocated the object and ran its configuration closure right then — even for tasks that would never run in this invocation. In a build with thousands of tasks this is wasteful: running `./gradlew help` still pays to configure your packaging, signing, and publishing tasks. ## Configuration avoidance Gradle introduced the **configuration-avoidance API** to fix this. The key entry points in the Kotlin DSL: - `tasks.register<T>("name") { ... }` — returns a `TaskProvider<T>`. The task is **not created** and the block **not executed** until something *realizes* the provider (it's scheduled to run, or `.get()`/an accessor forces it). - `tasks.named<T>("name") { ... }` — also returns a `TaskProvider<T>`; configures an existing registered task lazily. Contrast with the **eager** API: - `tasks.create<T>("name") { ... }` — returns the realized `T`, configured immediately. - `tasks.getByName("name")` — realizes and returns the task immediately. ## TaskProvider `register` hands back a `TaskProvider<T>`, a lazy reference. You can wire it into other tasks' inputs without realizing it, e.g. another task can depend on the provider directly. Only when the dependency is actually needed does Gradle create the underlying task. ```kotlin val myJar = tasks.register<Jar>("myJar") { archiveBaseName.set("app") from(sourceSets.main.get().output) } // no Jar instance exists yet; configuring assemble below does NOT realize myJar tasks.named("assemble") { dependsOn(myJar) } ``` ## Rule of thumb Use `register` for every task you define. Avoid `create`, `getByName`, `getByType`, and `tasks.all { }` (eager) unless a third-party plugin forces your hand. Prefer `named` over `getByName` and `withType<T>().configureEach { }` over `withType<T> { }`.

  • What type does register() return and why does that matter?
    A TaskProvider<T> — a lazy handle. It lets you wire dependencies and inputs without forcing the task to be created, preserving configuration avoidance.
  • If you only ever run `help`, does a registered Jar task get configured?
    No. Its configuration block runs only if the task is realized — queried via .get()/an accessor, or scheduled to run. `help` realizes neither, so the block never executes.

register is like adding a contact's name to your phone; create is calling the person immediately to introduce yourself even if you never need them.

saying these in an interview costs you the question

  • Saying create() and register() are interchangeable with no performance difference.
  • Claiming register() runs the configuration block immediately.

context