skip to content

How do you create configurations with explicit roles in modern Gradle, and why use the role-locking factories?

level: seniorimportance: should knowfreq 35%

answer

  1. dependencyScope / resolvable / consumable factories
  2. role-locked flags
  3. replaces create { isCanBeResolved = ... }
  4. returns lazy provider
  5. attributes on resolvable/consumable ends

basics

~10 s

Use the role-specific factories on the configurations container: dependencyScope("..."), resolvable("..."), and consumable("..."). They lock the role and the flags so the configuration can't be misused.

solid answer

~40 s

Modern Gradle (8.4+) exposes **role-locking factory methods** on the `ConfigurationContainer`: `configurations.dependencyScope("name")` for a bucket, `configurations.resolvable("name")` for something you resolve, and `configurations.consumable("name")` for a variant you expose. Each returns a typed provider and *locks* the `isCanBeResolved`/`isCanBeConsumed` flags so the role is fixed at creation — you can't later flip a bucket into a resolvable one, which removes a whole class of misuse and deprecation warnings. This replaces the old pattern of `configurations.create("name") { isCanBeResolved = true; isCanBeConsumed = false }`, where the flags were mutable and easy to set inconsistently. For custom plugins (e.g. exposing a custom variant), wire the bucket → resolvable/consumable with `extendsFrom` and set attributes on the resolvable/consumable ends. The factories make role intent explicit and self-documenting.

code

kotlin · 8 lines
kotlin
val deps = configurations.dependencyScope("customDeps")
val path = configurations.resolvable("customPath") {
    extendsFrom(deps.get())
}
val elements = configurations.consumable("customElements") {
    extendsFrom(deps.get())
    attributes { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, "custom")) }
}

go deeper

for a junior

Know the three factory names exist and map to the three roles.

for a middle

Use them to create a bucket + resolvable pair wired with extendsFrom.

for a senior

Build a full custom variant (bucket → resolvable + consumable with matching attributes and outgoing artifacts) and explain the locking benefit.

for a principal

Standardize these factories in shared convention plugins so every team produces role-correct, deprecation-free configurations.

## The old way (legacy) You created a configuration and hand-set the flags: ```kotlin val myResolvable = configurations.create("myResolvable") { isCanBeResolved = true isCanBeConsumed = false isCanBeDeclaredAgainst = false // older flag } ``` Problems: flags are mutable, easy to set inconsistently (both true), and the intent isn't obvious. Gradle now emits deprecation warnings for ambiguous combinations. ## The modern factories The `ConfigurationContainer` provides three role-locking factories (stable from Gradle 8.4): - `configurations.dependencyScope("name")` → bucket; both flags false. - `configurations.resolvable("name")` → `isCanBeResolved = true`, consumable false. - `configurations.consumable("name")` → `isCanBeConsumed = true`, resolvable false. They return `NamedDomainObjectProvider<…Configuration>` (lazy) and **lock** the role, so the flags can't drift. ## Wiring a custom variant A typical pattern: a bucket you declare into, a resolvable to consume it locally, and a consumable to expose it. ```kotlin val reportDeps = configurations.dependencyScope("reportDeps") val reportPath = configurations.resolvable("reportPath") { extendsFrom(reportDeps.get()) attributes { attribute(Usage.USAGE_ATTRIBUTE, objects.named("my-report")) } } val reportElements = configurations.consumable("reportElements") { extendsFrom(reportDeps.get()) attributes { attribute(Usage.USAGE_ATTRIBUTE, objects.named("my-report")) } outgoing.artifact(reportTask.flatMap { it.output }) } ``` Here the bucket holds declarations, the resolvable computes the local set, and the consumable publishes the variant with matching attributes so consumers can select it. ## Why prefer the factories - **Self-documenting**: the method name *is* the role. - **Safe**: flags are locked; no accidental dual-role. - **Future-proof**: avoids deprecation warnings as Gradle removes the legacy mutable-flag path. ## Takeaway Reach for `dependencyScope` / `resolvable` / `consumable` whenever you create configurations in a plugin or build script; only fall back to `create` for compatibility with very old Gradle.

  • What do the factory methods return, and why does that matter?
    They return a `NamedDomainObjectProvider` (lazy), so creation and configuration are deferred until needed — friendlier to configuration avoidance and the configuration cache than eager `create`.
  • Why set attributes only on the resolvable/consumable ends, not the bucket?
    The bucket never participates in variant selection; attributes drive resolution and consumption matching, so they belong on the resolvable (consumer side) and consumable (producer side) configurations.
  • When would you still use `configurations.create`?
    Mainly for compatibility with Gradle versions older than the role factories, or when intentionally migrating legacy configurations; for new code the role factories are preferred.

saying these in an interview costs you the question

  • Setting both `isCanBeResolved` and `isCanBeConsumed` true on a custom configuration.
  • Putting selection attributes on a dependency-scope bucket.
  • Calling `.get()` everywhere and forcing eager realization, defeating the lazy providers.

context