When you consume project(path: ':lib', configuration: 'foo'), what exactly do you receive — just artifacts, or dependencies too — and how does transitivity behave?
answer
- config = artifacts + dependencies
- dependencies on foo flow transitively to consumer
- isTransitive = false to opt out
- keep file-only configs free of extra deps
- same transitivity as attribute-driven path
basics
~10 sYou receive both the artifacts attached to 'foo' and the dependencies declared on 'foo', including their transitive closure. The named configuration's whole outgoing surface comes along, not just files.
solid answer
~50 sTargeting `project(path: ':lib', configuration: 'foo')` binds to the *entire* outgoing surface of `foo`: the artifacts published to it (via `artifacts {}` / `outgoing.artifact`) **and** the dependencies declared on it, resolved transitively. So if `foo` extends another configuration or declares its own dependencies, those flow into the consumer's graph. This is why a thin custom configuration intended to expose just one file should declare no extra dependencies — otherwise consumers inherit them. You can opt out of transitivity at the dependency declaration with `isTransitive = false`, or for a project dependency by excluding transitives, but by default the full closure is pulled. Contrast with attribute-driven consumption, where the selected variant similarly brings its dependencies, but the variant is chosen for you. The mental model: a consumable configuration is a *package* of artifacts plus dependency metadata, and naming it consumes the whole package.
code
kotlin · 6 lines// consumer opting out of transitive deps of the named configuration
dependencies {
implementation(project(path = ":lib", configuration = "foo")) {
isTransitive = false
}
}go deeper
Know that a named configuration carries more than just files.
State that both artifacts and declared dependencies (transitively) flow to the consumer.
Explain extendsFrom inheritance, transitivity defaults, opting out with isTransitive, and the classpath-bloat risk of leaky configs.
Guide teams to design narrow, dependency-clean consumable configurations and audit for accidental transitive leakage across modules.
## A configuration is artifacts + dependencies It's a common misconception that `project(configuration: 'foo')` only hands over files. In reality a Gradle configuration bundles two things that both flow to the consumer: 1. **Artifacts** — whatever was attached via `artifacts { add("foo", task) }` or `configurations.foo.outgoing.artifact(...)`. 2. **Dependencies** — anything declared directly on `foo`, plus anything it `extendsFrom`, resolved **transitively** by default. ```kotlin val foo by configurations.creating { isCanBeConsumed = true isCanBeResolved = false } dependencies { add("foo", "com.google.guava:guava:33.0.0-jre") // <-- flows to consumers! } artifacts { add("foo", tasks.named("buildFooJar")) } ``` A consumer doing `implementation(project(path = ":lib", configuration = "foo"))` gets the foo jar **and** Guava (and Guava's transitives). ## Controlling transitivity If you don't want the closure, you can declare the dependency non-transitive: ```kotlin dependencies { implementation(project(path = ":lib", configuration = "foo")) { isTransitive = false } } ``` or add targeted `exclude(group = ..., module = ...)` rules. By default, though, transitivity is on. ## Practical implication When you design a custom consumable configuration meant to expose **only** a file (no dependency leakage), keep it from extending classpath configurations and declare nothing on it. Otherwise consumers silently inherit your internal dependencies — a frequent source of accidental classpath bloat and version conflicts. ## Relation to attribute-driven consumption With `project(":lib")` (no configuration named), Gradle still pulls the selected variant's dependencies transitively — the difference is only *how the variant is chosen* (attributes vs explicit name), not whether dependencies travel. So the transitivity behavior is consistent; explicit naming just removes the matching step.
- Why might a custom consumable configuration accidentally leak dependencies into consumers?Because dependencies declared on (or inherited via extendsFrom by) that configuration travel transitively to anything consuming it. A config meant to expose only a file should declare no extra dependencies and not extend classpath configs.
- How do you consume only the file artifact and none of the configuration's dependencies?Declare the project dependency with isTransitive = false (or add exclude rules), so only the directly attached artifacts are pulled in.
saying these in an interview costs you the question
- Asserting that project(configuration:) brings only artifacts and never dependencies.
- Assuming transitivity is off by default for project dependencies.