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?
answer
- Task vs TaskProvider return type
- side effects now lazy or never run
- must delaze getByName/all/withType too
- move side effects to plugin apply
- verify with --scan / config cache
basics
~20 screate 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.
solid answer
~50 s`register` isn't a pure drop-in for `create` because it changes *when* and *whether* configuration runs. Three things bite: (1) **Return type** — `create` returns a `Task`/typed instance, `register` returns `TaskProvider<T>`; any caller doing `create(...).someProperty` must switch to `provider.configure {}`, `map`, or move logic into the block. (2) **Timing of side effects** — if a `create` block had side effects (registering extensions, mutating other tasks, eager `dependsOn` to a concrete task), those now run lazily or never; ordering assumptions can break. (3) **Forced realization elsewhere** — other code using `getByName`/`all`/iteration will still realize the task, so you only get the win if you also delaze the surrounding wiring. Practically: register the task, replace `getByName` with `named`, `all`/`withType {}` with `configureEach`, and read outputs via `map`/`flatMap`. Then verify with a build scan that `gradle help` no longer realizes unrelated tasks. Also confirm configuration-cache compatibility, which `register` patterns support better.
code
kotlin · 8 lines// before: eager, direct property access, side effect at config time
val jar = tasks.create("jar", Jar::class)
jar.archiveFileName.set("app.jar")
project.extensions.add("built", true) // ran every build
// after: lazy; side effect moved OUT of any task block
val jar = tasks.register("jar", Jar::class) { archiveFileName.set("app.jar") }
project.extensions.add("built", true) // keep in plugin apply, not in a lazy blockgo deeper
Recognize that the return type changes from Task to TaskProvider and that you can't just swap the call.
Explain the three pitfalls (return type, deferred side effects, surrounding eager APIs) and the lazy replacements.
Drive a whole-graph migration: relocate side effects to apply, delaze wiring, and verify with build scans and config-time profiling.
Tie the migration to configuration-cache adoption and build-scalability goals; sequence it across many modules and enforce lazy patterns in shared plugins.
## Why it's not mechanical `tasks.create` does two things eagerly: returns a usable `Task` *and* runs configuration now. `tasks.register` does neither up front. Swapping them changes the program's observable behavior in subtle ways. ## 1. Return type change ```kotlin // before val jar: Jar = tasks.create("jar", Jar::class) jar.archiveFileName.set("app.jar") // direct property access // after val jar: TaskProvider<Jar> = tasks.register("jar", Jar::class) jar.configure { archiveFileName.set("app.jar") } // must configure lazily ``` Any downstream code that treated the result as a concrete `Task` must change to `configure {}`, `map`, or `flatMap`. ## 2. Side effects move (or vanish) If a legacy `create` block did things like: ```kotlin tasks.create("setup") { project.extensions.add("foo", Foo()) // side effect at config time } ``` with `register`, that side effect runs **only when `setup` is realized** — which may be never for a build that doesn't run `setup`. Side effects belong in the plugin's `apply`, not in a (now-lazy) task block. Hidden ordering dependencies ("task A's block configured task B") similarly break. ## 3. Realization leaks defeat the migration Replacing `create` with `register` buys nothing if `tasks.getByName("x")`, `tasks.all { }`, `withType(Foo) { }`, or container iteration elsewhere still realizes the task. Migration is a *whole-graph* change: | Replace | With | |---|---| | `create("x")` | `register("x")` | | `getByName("x")` | `named("x")` | | `all { }` | `configureEach { }` | | `withType(T) { }` | `withType(T).configureEach { }` | | `t.output` (concrete) | `provider.flatMap { it.output }` | ## 4. Verify the win ```bash ./gradlew help --scan # inspect task-creation timeline ./gradlew help --profile # configuration time before/after ``` If `gradle help` realizes tasks unrelated to help, a leak remains. ## 5. Bonus: configuration cache Lazy `register`/provider patterns are friendlier to the **configuration cache** (which serializes the task graph). Eager `create` plus concrete-instance capture often triggers config-cache problems, so the migration usually pays off twice. ## Checklist - Switch return-type usages to provider APIs. - Move config-time side effects out of task blocks. - Delaze surrounding wiring (named/configureEach/flatMap). - Re-scan to confirm fewer realizations and stable config time.
- A legacy create block configured another task as a side effect. Why is that dangerous after migrating to register?Because the block now runs only when the task is realized; if the build never realizes it, the other task is never configured, silently changing behavior. Such cross-task setup should live in plugin apply, not a lazy task block.
- How does migrating to register help the configuration cache?Lazy registration and provider wiring avoid capturing concrete Task/Project state at configuration time, which is exactly what the configuration cache forbids serializing; eager create patterns more often trip cache incompatibilities.
- Why might the migration show no speedup?Because eager APIs elsewhere (getByName, all, withType {}, iteration) still realize the registered tasks; you must delaze the whole wiring for the configuration-avoidance benefit to appear.
saying these in an interview costs you the question
- Treating register as a textual find-replace for create.
- Leaving config-time side effects inside now-lazy task blocks.
- Declaring victory without re-checking realizations via a build scan.