skip to content

When would you use in-script ext/extra properties versus gradle.properties or typed providers, and what are the trade-offs?

level: seniorimportance: should knowfreq 38%

answer

  1. ext: ad-hoc, untyped, eager, in-script
  2. gradle.properties: externalised, overridable, strings
  3. providers: typed, lazy, cache-safe, task inputs
  4. trend = away from ext toward providers/catalogs
  5. ext dumping ground = config-cache hostile

basics

~20 s

Use ext for ad-hoc values computed in-script and shared across the build. Use gradle.properties for externalised/overridable config (and CI/-P overrides). Prefer typed providers (Property/Provider) when a value feeds tasks and must stay lazy and cache-friendly.

solid answer

~50 s

These three serve different needs. **ext/extra** are dynamic, untyped, in-script properties — great for quick sharing of computed values (a timestamp, a derived version) across a build, but type-unsafe and resolved eagerly. **gradle.properties** holds externalised, file-based project properties — overridable by `-P`, environment, or the gradle.properties in the user home; ideal for things operators or CI must change without editing scripts (this leaf only contrasts it; it is owned by the gradle.properties leaf). **Typed providers** (`Property<T>`, `Provider<T>`, `providers.gradleProperty(...)`) are the modern choice for anything wired into tasks: they are type-safe, lazy, track inputs, and play well with the configuration cache. Rule of thumb: reach for ext only for informal configuration-time sharing; use gradle.properties for externally tunable knobs; use providers when the value crosses into task inputs or must be evaluated lazily. Overusing ext leads to untyped, error-prone, cache-hostile builds.

code

kotlin · 11 lines
kotlin
// ext: informal, eager
val buildStamp by extra(java.time.Instant.now().toString())

// gradle.properties value, read lazily & typed via a provider (preferred for task inputs)
val springVersion = providers.gradleProperty("springVersion").getOrElse("6.1.0")

tasks.register("printInfo") {
    // capture the provider, not an eagerly-read ext value -> cache friendly
    val v = springVersion
    doLast { println("spring=$v stamp=${project.extra["buildStamp"]}") }
}

go deeper

for a junior

Know that ext is in-script and gradle.properties is a file; pick file-based for things you override externally.

for a middle

Explain the eager vs lazy and typed vs untyped distinctions and give a basic decision guide.

for a senior

Reason about configuration cache, task-input wiring, and the shift toward providers and version catalogs over ext.

for a principal

Set build-platform policy: standardise on catalogs/providers/convention plugins, treat ad-hoc ext as tech debt, and govern overridable config via gradle.properties layering.

## Three mechanisms, three jobs ### 1. ext / extra (this leaf) Dynamic, untyped key/value pairs attached to an `ExtensionAware` object in the script itself. ```kotlin val buildStamp by extra(java.time.Instant.now().toString()) ``` - **Pros:** trivial to write; share computed values across projects; no files. - **Cons:** no type safety (typo or wrong type fails at execution); resolved eagerly so capturing into task config can break the configuration cache; invisible to tooling/IDE as typed. ### 2. gradle.properties (contrast only — owned by sibling leaf) File-based **project properties**, layered: project root `gradle.properties`, `GRADLE_USER_HOME/gradle.properties`, `-Pkey=value`, env `ORG_GRADLE_PROJECT_key`. ```properties springVersion=6.1.0 org.gradle.caching=true ``` - **Pros:** externalised, overridable without editing scripts, perfect for CI/secrets-ish toggles and Gradle daemon/cache settings. - **Cons:** all values are strings; still resolved as plain values unless read via providers. ### 3. Typed providers / properties The lazy-configuration API. ```kotlin val version: Provider<String> = providers.gradleProperty("springVersion") // or a managed property on an extension abstract val flavour: Property<String> ``` - **Pros:** type-safe, lazy (evaluated at execution, not configuration), declared as task inputs for up-to-date checking, configuration-cache compatible. - **Cons:** more ceremony; needs an owning extension/task for `Property<T>`. ## Decision guide | Need | Use | |---|---| | Compute a value once and reference it across the script/subprojects | ext/extra | | Operator/CI must override a value without code changes | gradle.properties (+ -P/env) | | Value feeds a task input and must stay lazy & cache-safe | Provider/Property (e.g. providers.gradleProperty) | | Structured, type-safe configuration surface for a plugin | typed extension with managed properties | ## Why senior interviewers care The anti-pattern is treating `ext` as a dumping ground: untyped strings flowing into task configuration, read eagerly, breaking the configuration cache and producing late, confusing failures. A senior answer recognises that the trend across Gradle 7/8 is **toward providers and version catalogs** and **away from dynamic ext**, reserving ext for genuinely ad-hoc, configuration-time-only sharing. ## Bridging You can read a gradle.property lazily and avoid ext entirely: ```kotlin val springVersion = providers.gradleProperty("springVersion").getOrElse("6.1.0") ``` This is type-aware (String), lazy, and cache-friendly — usually preferable to stashing the same thing in ext.

  • Why can heavy use of ext properties hurt the configuration cache?
    ext values are read eagerly as plain objects during configuration; capturing them into task actions ties task behaviour to configuration-time state rather than a serializable Provider, which the configuration cache cannot reliably reuse.
  • If a value must be overridable on the command line, would you put it in ext?
    No. Command-line/env overrides are the job of gradle.properties with -P/ORG_GRADLE_PROJECT_, ideally read through providers.gradleProperty so it is typed and lazy.
  • What modern Gradle feature largely replaces ext for sharing dependency versions?
    Version catalogs (libs.versions.toml) provide typed, centralised, IDE-aware version and library declarations, replacing the old pattern of stashing versions in ext.

saying these in an interview costs you the question

  • Recommending ext as the default place for all shared config.
  • Saying gradle.properties and ext are interchangeable.
  • Ignoring configuration-cache / laziness implications when choosing.

context