skip to content

Locating Tasks: named & withType

Locating existing tasks lazily by name, by type, or with a matching predicate instead of reaching for getByName. Asked because it is the everyday, practical face of configuration avoidance in real build scripts.

on this pageshow

questions

5

How do you select an existing task by name in a Gradle build script, and why is tasks.named() preferred over tasks.getByName()?

level: juniorimportance: must knowfreq 70%

answer

  1. named() returns TaskProvider (lazy)
  2. getByName() realizes immediately
  3. typed named<JavaCompile> avoids cast
  4. deferred config runs only if task realized
  5. configuration-avoidance API

basics

~10 s

Use tasks.named('compileJava') to get a lazy reference. It is preferred over getByName() because named() doesn't force the task to be configured immediately — configuration runs only if the task is actually needed.

solid answer

~40 s

`tasks.named('compileJava')` returns a `TaskProvider`, a lazy handle to the task. Any configuration you pass — `tasks.named<JavaCompile>('compileJava') { options.encoding = "UTF-8" }` — is deferred and runs only if the task ends up in the execution graph. `tasks.getByName('compileJava')` instead returns the realized `Task` *immediately*, forcing it to be created and configured during the configuration phase even when it will never run. On a large multi-project build, eagerly realizing every task you touch inflates configuration time. `named()` is part of Gradle's configuration-avoidance API; the typed form `named<JavaCompile>(...)` also gives you compile-time access to the task's properties. Rule of thumb: reach for `named()`, never `getByName()`, in new code.

code

kotlin · 7 lines
kotlin
// Lazy — preferred
tasks.named<JavaCompile>("compileJava") {
    options.encoding = "UTF-8"
}

// Eager — avoid: realizes & configures compileJava right now
tasks.getByName("compileJava")

go deeper

for a junior

Know that named() is the lazy way to get a task and is preferred over getByName(); be able to write a basic named() block.

for a middle

Explain the configuration-phase realization difference and use the typed named<T>() form to avoid casts.

for a senior

Articulate the configuration-avoidance rationale and how named() composes with register() and configureEach for a fast configuration phase.

for a principal

Set conventions banning eager accessors in a large monorepo and explain measurable configuration-time impact across many subprojects.

## The problem: eager vs lazy task access A Gradle build has two main phases: **configuration** (every build script runs, the task graph is built) and **execution** (only the requested tasks plus their dependencies actually run). Historically the task container exposed *eager* accessors — `tasks.getByName('x')`, `tasks.create('x')`, `tasks['x']` — that **realize** a task: they create the task object and run all of its configuration code right then, during the configuration phase, regardless of whether the task will ever execute. In a build with hundreds of tasks across many subprojects, realizing tasks you merely *referenced* wastes time on every invocation, including `./gradlew help`. ## tasks.named() — the lazy locator `tasks.named('compileJava')` returns a **`TaskProvider<Task>`**, a lazy reference. The actual task is realized only if and when something needs it — typically because it (or a task depending on it) is part of the execution graph. Configuration supplied through the provider is also deferred: ```kotlin tasks.named<JavaCompile>("compileJava") { options.encoding = "UTF-8" // runs only if compileJava is realized } ``` The **typed** overload `named<JavaCompile>("compileJava")` (Kotlin) / `named("compileJava", JavaCompile)` (Groovy) returns a `TaskProvider<JavaCompile>`, so inside the configuration block you get statically-typed access to `JavaCompile`-specific properties without a cast. If the name doesn't exist, `named()` fails fast with an `UnknownTaskException`. ## getByName() — the eager locator `tasks.getByName('compileJava')` returns a realized `Task` *now*. Use it only when you genuinely need the concrete task object immediately and lazy access is impossible (rare). In modern builds it is a code smell. ## Why it matters Configuration avoidance keeps the configuration phase cheap. Combined with `register()` (lazy creation) and `withType().configureEach {}` (lazy bulk configuration), `named()` lets a build configure only the slice of the task graph that a given invocation actually touches. ## Summary - `named(name)` → lazy `TaskProvider`; deferred configuration. - `named<T>(name)` → typed provider, no cast needed. - `getByName(name)` → eager `Task`, realized immediately — avoid.

  • What exception does named() throw if the task name doesn't exist?
    UnknownTaskException — it fails fast during configuration rather than returning null.
  • Does named() guarantee the task is never configured?
    No — it defers configuration. If the task ends up in the execution graph (requested or a dependency), it is realized and the deferred config block runs.

saying these in an interview costs you the question

  • Claiming named() and getByName() are interchangeable with no performance difference.
  • Saying named() returns the Task object directly (it returns a TaskProvider).

context

open as a page

Explain tasks.withType<Test>().configureEach { } — what does it do, and why use configureEach instead of all() or each()?

level: middleimportance: must knowfreq 60%

basics

~10 s

withType<Test>() selects all tasks of type Test, and configureEach applies the block to each — both existing and future ones — lazily. configureEach defers configuration; all()/each() would eagerly realize every matching task.

open as a page

When would you use tasks.matching { } to select tasks, and what are its trade-offs compared to withType()?

level: middleimportance: should knowfreq 40%

basics

~20 s

tasks.matching { } returns a live collection of tasks satisfying an arbitrary predicate — e.g. by name prefix. Use it when a type filter isn't enough. The trade-off: its predicate is less configuration-avoidance-friendly than withType's pure type filter.

open as a page

What is the benefit of the typed form tasks.named<JavaCompile>('compileJava') over the untyped tasks.named('compileJava'), and what happens if the type is wrong?

level: middleimportance: should knowfreq 35%

basics

~10 s

The typed form returns a TaskProvider<JavaCompile>, giving statically-typed access to JavaCompile properties inside the block — no cast needed. If the actual task isn't that type, Gradle fails when the task is realized.

open as a page

A team's configuration phase is slow. They use tasks.getByName(), withType().all{}, and iterate tasks eagerly to configure them. How would you diagnose and fix this using lazy task-location APIs?

level: seniorimportance: should knowfreq 45%

basics

~10 s

These eager accessors realize every referenced task during configuration, even unused ones. Replace getByName() with named(), withType().all{} with withType().configureEach{}, and remove eager iteration. Measure with a build scan or --profile before and after.

open as a page