How does task configuration change when migrating from the Groovy DSL to the Kotlin DSL — for example configuring the `test`, `jar`, or a custom task?
answer
- test { } -> tasks.named<Test>("test") { }
- task foo(type:) -> tasks.register<T>("foo")
- register/named = lazy, create = eager
- supply <Type> for accessor/auto-complete
- .set(...) for Property, = for plain props
basics
~20 sGroovy's loose task foo and test { } become typed Kotlin calls. Use tasks.named("test") { ... } to configure an existing task and tasks.register("foo") { ... } to create one, with property assignments using =.
solid answer
~50 sGroovy lets you write `test { useJUnitPlatform() }` or `task myTask { ... }` thanks to dynamic dispatch. In the Kotlin DSL those become **typed, lazy** task APIs: - **Configure an existing task:** `tasks.named<Test>("test") { useJUnitPlatform() }` — `named` returns a `TaskProvider` and configures lazily. For common typed tasks Gradle also generates accessors, e.g. `tasks.test { }` and `tasks.jar { }`. - **Create a task:** `tasks.register<Copy>("copyDocs") { ... }` instead of Groovy's `task copyDocs(type: Copy) { ... }`. `register` is lazy; the older `tasks.create` is eager and discouraged. - **Properties:** Groovy's permissive setters become Kotlin assignments to typed properties (`archiveFileName.set("app.jar")` for a `Property`, or `=` for a plain property). Giving Kotlin the task **type** (`<Test>`, `<Copy>`) is what restores the type-safe configuration closure and auto-completion that Groovy gave you dynamically. The lazy `named`/`register` pair is also the recommended pattern regardless of DSL because it avoids configuring tasks that never run.
code
kotlin · 13 lines// Groovy:
// test { useJUnitPlatform() }
// task zipReports(type: Zip) { from 'reports'; archiveFileName = 'r.zip' }
// Kotlin DSL:
tasks.named<Test>("test") {
useJUnitPlatform()
}
tasks.register<Zip>("zipReports") {
from("reports")
archiveFileName.set("r.zip") // Property -> .set(...)
}go deeper
Know tasks.named to configure and tasks.register to create, with the task type in angle brackets.
Explain eager vs lazy and the Property.set() vs = distinction.
Tie the migration to configuration-avoidance and reproducible, type-safe task wiring.
Advocate codifying these task patterns in convention/precompiled-script plugins so every module is consistent.
## The dynamic-to-typed shift Groovy resolves `test { ... }` and `task foo(type: Jar) { ... }` at runtime via metaprogramming. The Kotlin DSL can't do that — it needs **types at compile time**. So every task interaction goes through the typed `tasks` container API, and you supply the task type as a generic parameter. ## Configuring an existing task Groovy: ```groovy test { useJUnitPlatform() maxParallelForks = 4 } ``` Kotlin: ```kotlin tasks.named<Test>("test") { useJUnitPlatform() maxParallelForks = 4 } ``` Gradle also generates **type-safe accessors** for tasks contributed by applied plugins, so you can often shorten this to `tasks.test { ... }` or `tasks.jar { ... }`. The `<Test>` generic gives the closure the correct receiver type, restoring auto-completion. ## Creating a task Groovy: ```groovy task copyDocs(type: Copy) { from 'src/docs' into "$buildDir/docs" } ``` Kotlin: ```kotlin tasks.register<Copy>("copyDocs") { from("src/docs") into(layout.buildDirectory.dir("docs")) } ``` ## Eager vs lazy — the important part - `tasks.create(...)` / Groovy `task ...` are **eager**: the task is created and configured immediately during configuration time, even if it never runs. - `tasks.register(...)` and `tasks.named(...)` are **lazy** (the configuration-avoidance API): the configuration block runs only if the task is actually needed. This is the modern best practice and the migration is a good moment to adopt it. ## Property assignment differences Many modern task properties are lazy `Property<T>`/`RegularFileProperty` types. In Groovy you can assign them with `=` (Gradle adds magic setters). In Kotlin you usually call `.set(...)`: ```kotlin tasks.jar { archiveFileName.set("app.jar") } ``` Plain (non-lazy) properties still use `=`. Mixing these up is the most common migration error. ## Summary Task config migration = (1) route through `tasks.named`/`tasks.register`, (2) supply the task **type** for type safety, (3) prefer the **lazy** API, (4) use `.set(...)` for lazy `Property` values.
- Why prefer tasks.register over tasks.create when migrating?register is part of the configuration-avoidance API: its configuration block runs lazily only if the task is needed, whereas create eagerly configures every task at configuration time, slowing the build.
- Why does archiveFileName use .set(...) in Kotlin but = worked in Groovy?archiveFileName is a lazy Property<String>. Groovy's DSL adds a convenience setter so = works; the Kotlin DSL exposes the real API, so you call .set(...). Plain non-lazy properties still use =.
saying these in an interview costs you the question
- Using tasks.create for new tasks during migration (eager, against current guidance).
- Assigning a lazy Property with = in Kotlin and being surprised it doesn't compile.
- Omitting the task type generic and then complaining auto-completion is missing.