skip to content

Why should you prefer tasks.named<Test>("test") { } over tasks.getByName("test") { } when configuring an existing task in the Kotlin DSL?

level: middleimportance: must knowfreq 60%

answer

  1. named = lazy lookup
  2. getByName = eager lookup
  3. typed receiver via named<Test>
  4. deferred configuration action
  5. realized only when needed

basics

~10 s

named() configures the task lazily via a TaskProvider, so it's only realized if needed. getByName() realizes the task immediately, defeating configuration avoidance.

solid answer

~40 s

`tasks.named<Test>("test") { ... }` returns a `TaskProvider<Test>` and registers a *deferred* configuration action: the block runs only when the `test` task is actually realized. `tasks.getByName("test") { ... }` looks the task up *eagerly*, forcing it to be created and its configuration to run right now, even on unrelated invocations. The typed `named<Test>` overload also gives you a `Test`-typed receiver inside the block, so you get type-safe access to properties like `maxParallelForks` and `useJUnitPlatform()` without casting. The rule mirrors register-vs-create: `named` is the configuration-avoidance way to touch an existing task; `getByName` is the eager escape hatch you avoid unless a plugin forces it.

code

kotlin · 8 lines
kotlin
// Lazy + type-safe receiver
tasks.named<Test>("test") {
    useJUnitPlatform()
    maxParallelForks = Runtime.getRuntime().availableProcessors()
}

// Eager — avoid
tasks.getByName<Test>("test").maxParallelForks = 4

go deeper

for a junior

Recall that named is the lazy way to configure an existing task and is preferred.

for a middle

Explain deferred configuration actions, the typed receiver, and what triggers realization.

for a senior

Discuss keeping whole dependency chains lazy via providers and auditing scripts for stray getByName/getByType.

for a principal

Drive a lint/convention enforcing lazy lookups across all build logic and convention plugins; quantify configuration-time savings.

## Configuring tasks others registered Plugins register many tasks for you — `test`, `jar`, `compileKotlin`, `assemble`. When you need to tweak one, you must *look it up*. The Kotlin DSL offers two families: ### Lazy (preferred) - `tasks.named("test")` → `TaskProvider<Task>` - `tasks.named<Test>("test")` → `TaskProvider<Test>` (typed) - `tasks.named<Test>("test") { ... }` → registers a deferred configuration action and returns the provider The configuration block is stored and only executed when the task is *realized* (scheduled to run or forced via `.get()`). On a `help` invocation, the `test` task is never realized, so your tweak costs nothing. ### Eager (avoid) - `tasks.getByName("test")` → realizes and returns the `Task` now - `tasks.getByName<Test>("test")` → same, typed - `tasks.getByName("test") { ... }` → realizes now and configures now ## Type safety The typed overload `named<Test>("test")` not only stays lazy but supplies a correctly typed receiver. Without it you'd get a `Task` and have to cast: ```kotlin // typed + lazy — best tasks.named<Test>("test") { useJUnitPlatform() maxParallelForks = 4 } ``` ## Realization rules A `TaskProvider` is realized when: - the task is on the execution graph (requested or a dependency of a requested task), - you call `.get()` on the provider, - a type-safe accessor like `tasks.test` is referenced (these are eager today), - an eager API such as `getByName`, `withType<T> { }` (non-configureEach), or `tasks.all { }` runs. ## Wiring without realizing You can use the returned provider to express dependencies or input wiring without realizing the task: ```kotlin val testTask = tasks.named<Test>("test") tasks.named("check") { dependsOn(testTask) } ``` This keeps the whole chain lazy until the build actually needs `check`.

  • Does named() execute its configuration block at the point you call it?
    No. It records a deferred configuration action that runs only when the task is realized. The call itself returns a provider immediately.
  • What advantage does the typed named<Test>("test") overload give beyond laziness?
    A Test-typed receiver inside the block, so properties like maxParallelForks and useJUnitPlatform() are available without an explicit cast.
  • When would getByName still be justified?
    When a legacy plugin's API hands you no provider, or you genuinely need the realized instance synchronously — rare in modern builds.

saying these in an interview costs you the question

  • Saying named() and getByName() behave identically.
  • Believing named()'s block runs immediately when you call named().

context