skip to content

Extra properties exist on objects other than Project. How do per-object ext namespaces (e.g. on a Task) work, and when is that useful?

level: seniorimportance: nice to knowfreq 20%

answer

  1. ext on any ExtensionAware: Task/Gradle/Settings/SourceSet
  2. each namespace independent (no inheritance)
  3. task.extra["k"] / it.ext.has()
  4. good for ad-hoc tagging/metadata
  5. use @Input typed props for real task inputs

basics

~20 s

Any ExtensionAware object — Task, Gradle, Settings, SourceSet — has its own ext. So task.ext.foo is separate from project.ext.foo. It lets you attach ad-hoc metadata to that specific object, though typed inputs are usually better.

solid answer

~50 s

Extra properties aren't a project-only feature: every `ExtensionAware` object carries its own `ExtraPropertiesExtension`. A `Task`, the `Gradle` object, `Settings`, and `SourceSet`s each have an independent `ext` namespace. `someTask.ext.foo = 1` stores `foo` on that task only — it is invisible from `project.ext` and from other tasks. This is occasionally handy for attaching ad-hoc metadata to a particular object — for example tagging tasks so a later `tasks.matching { it.ext.has("deploys") }` can select them — when no typed property exists. In the Kotlin DSL you reach it via `someTask.extra["foo"]`. The caveats mirror project ext: untyped, eager, fails at execution on typos. For task configuration, a typed managed property (`@Input val ...`) or a custom task type is almost always preferable because it gives type safety and proper up-to-date tracking; per-object ext is best reserved for lightweight, non-input metadata.

code

kotlin · 11 lines
kotlin
tasks.register("deployStaging") {
    extra["environment"] = "staging"
    extra["requiresApproval"] = true
}

// branch on the ad-hoc metadata of each task
tasks.configureEach {
    if (extensions.extraProperties.has("requiresApproval")) {
        logger.lifecycle("$name needs approval")
    }
}

go deeper

for a junior

Know ext isn't project-only — tasks and other objects have their own bag.

for a middle

Explain per-object isolation and a concrete metadata/tagging use case with has().

for a senior

Contrast ext metadata with typed @Input task properties and justify when each is appropriate, including cache/up-to-date concerns.

for a principal

Discourage ext-based task state in shared build logic, favouring typed task types and managed properties for maintainable, cache-correct builds.

## ExtensionAware is everywhere The `ext`/`extra` mechanism is provided by `ExtensionAware`, and many Gradle objects implement it, not just `Project`: - `Project` - `Task` - `Gradle` (the build-level object) - `Settings` (in settings.gradle[.kts]) - `SourceSet`, `Configuration`, and other domain objects in many cases Each has an **independent** `ExtraPropertiesExtension`. There is no inheritance between, say, a task's ext and its project's ext. ## Per-task example ```kotlin val deploy = tasks.register("deployStaging") { extra["environment"] = "staging" extra["requiresApproval"] = true } // later: select tasks by their ad-hoc metadata tasks.matching { it.extensions.extraProperties.has("requiresApproval") } .configureEach { /* wire approval gate */ } ``` Groovy equivalent: ```groovy task deployStaging { ext.environment = 'staging' } tasks.matching { it.hasProperty('environment') }.all { /* ... */ } ``` ## Settings and Gradle scope In `settings.gradle.kts` you can stash values on the `Settings` object's ext; on the `Gradle` object (`gradle.ext`) you can keep build-wide ad-hoc state. These are niche but appear in plugin code that needs to pass a flag from settings evaluation into project configuration. ## When it's useful - **Tagging/metadata** an object so other configuration logic can find or branch on it, when no typed model exists. - **Bridging** a value between phases/objects in a pinch. ## When NOT to use it - **Task inputs/outputs** — use `@Input`/`@OutputFile` typed properties on a real task type so Gradle tracks up-to-date checks and the value is type-safe and cache-correct. - Anything that benefits from IDE/type support. ## Caveats recap Untyped (`Any?`), eager, per-object isolation, and runtime-only failure on a missing/typo'd key (`UnknownPropertyException`). Use `has(name)` to test existence safely. Treat per-object ext as a lightweight escape hatch for metadata, not a substitute for a proper typed model.

  • Is a task's ext property visible from project.ext?
    No. Each ExtensionAware object has its own isolated ExtraPropertiesExtension; task.ext and project.ext do not share entries and there is no inheritance between them.
  • Why prefer a typed @Input property over a task ext property for a build input?
    Typed managed properties give compile-time type safety, participate in up-to-date checking and the configuration cache, and are visible to tooling — ext is untyped, eager, and not tracked as an input.

saying these in an interview costs you the question

  • Claiming ext lives only on Project.
  • Assuming a task inherits its project's ext entries.
  • Using task ext for declared inputs/outputs instead of @Input/@Output properties.

context