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?
answer
- ext on any ExtensionAware: Task/Gradle/Settings/SourceSet
- each namespace independent (no inheritance)
- task.extra["k"] / it.ext.has()
- good for ad-hoc tagging/metadata
- use @Input typed props for real task inputs
basics
~20 sAny 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 sExtra 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 linestasks.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
Know ext isn't project-only — tasks and other objects have their own bag.
Explain per-object isolation and a concrete metadata/tagging use case with has().
Contrast ext metadata with typed @Input task properties and justify when each is appropriate, including cache/up-to-date concerns.
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.