skip to content

How do you depend on a *specific* named configuration of another project in the same build, rather than its default API/runtime, and what does the syntax look like?

level: middleimportance: must knowfreq 45%

answer

  1. project(path:, configuration:) two-arg form
  2. bypasses attribute matching
  3. producer config must be canBeConsumed
  4. Kotlin uses named args path =, configuration =
  5. testFixtures() is the attribute-driven shortcut

basics

~10 s

Use the two-arg project notation: project(path: ':lib', configuration: 'someConfig'). The configuration name selects which producer configuration to consume instead of the default one.

solid answer

~40 s

A plain `project(':lib')` dependency resolves against the producer's default outgoing variant (its API/runtime, chosen by attribute matching). To pull a *named* producer configuration instead, you use the explicit notation `project(path: ':lib', configuration: 'someConfig')` (Groovy) or `project(path = ":lib", configuration = "someConfig")` (Kotlin). This bypasses attribute-based variant selection and binds directly to the artifacts and dependencies that the named configuration exposes. It's the classic way to consume a non-default output of a sibling project — for example a `testFixtures`-style configuration, packaged client artifacts, or generated resources. Modern Gradle prefers attribute-driven selection (a single `project(':lib')` with attributes deciding the variant), but the explicit `configuration:` form is still valid and sometimes the simplest way to grab a specific output.

code

kotlin · 7 lines
kotlin
dependencies {
    // default variant (attribute-driven)
    implementation(project(":lib"))

    // a specific named producer configuration
    implementation(project(path = ":lib", configuration = "instrumentedJars"))
}

go deeper

for a junior

Recall that there is a two-arg form project(path:, configuration:) that picks a named configuration instead of the default.

for a middle

Explain the syntax in both Groovy and Kotlin and that it bypasses attribute matching, and note the producer config must be consumable.

for a senior

Contrast explicit configuration targeting with attribute-driven variant selection and explain when each is appropriate; mention testFixtures() as the higher-level helper.

for a principal

Frame a policy: prefer attribute/capability-driven consumption for maintainability, reserve explicit configuration targeting for narrow interop cases, and discuss migration paths off brittle explicit wiring.

## The problem When you write a project dependency in a multi-project build: ```kotlin dependencies { implementation(project(":lib")) } ``` Gradle has to decide *which* set of artifacts and transitive dependencies from `:lib` you actually get. By default it performs **variant-aware resolution**: it looks at the consumer's request attributes (e.g. `org.gradle.usage = java-api` vs `java-runtime`) and matches them against the producer's *consumable* configurations to pick exactly one variant. You never name a configuration — the attributes do the work. ## Targeting a specific configuration Sometimes you want to bypass that and grab a *named* output explicitly. The two-argument project notation does this: ```kotlin dependencies { implementation(project(path = ":lib", configuration = "instrumentedJars")) } ``` This says: "resolve `:lib`, but take exactly the configuration named `instrumentedJars` — its artifacts and its dependencies." Attribute matching is **not** used to choose the variant in this case; you've named it directly. ## What the producer must do For this to work, `:lib` must expose a configuration that is **consumable** (`canBeConsumed = true`) and carries the artifacts you want: ```kotlin // in :lib val instrumentedJars by configurations.creating { isCanBeConsumed = true isCanBeResolved = false } artifacts { add("instrumentedJars", instrumentJarTask) } ``` A configuration that is only a *bucket* (both `canBeConsumed` and `canBeResolved` false) or only *resolvable* cannot be consumed this way. ## When to use it vs attributes - **Explicit `configuration:`** — quick, direct, no attribute plumbing. Downside: brittle, ignores variant rules, and won't compose with attribute-based selection elsewhere. - **Attribute-driven** (plain `project(":lib")` + matching attributes) — the modern, recommended path; it's what plugins like `java-test-fixtures` build on top of via the `testFixtures(...)` helper. ## Test fixtures shortcut The `java-test-fixtures` plugin generates a `testFixtures` capability and gives you a helper: ```kotlin dependencies { testImplementation(testFixtures(project(":lib"))) } ``` That's attribute/capability driven under the hood — you rarely need the raw `configuration = "testFixtures..."` form once the plugin is applied.

  • What property must the producer configuration have for project(configuration:) to resolve against it?
    It must be consumable — isCanBeConsumed = true. A pure bucket or a resolvable-only configuration cannot be targeted this way.
  • What is the Kotlin DSL equivalent of the Groovy project(path: ':lib', configuration: 'x')?
    project(path = ":lib", configuration = "x") — Kotlin uses named arguments with = instead of Groovy's map-literal colon syntax.

saying these in an interview costs you the question

  • Saying project(':lib') always gives you 'all' the lib's outputs — it gives one attribute-selected variant.
  • Claiming you can target any configuration regardless of its canBeConsumed flag.

context