Explain how a source set's compile and runtime classpaths relate to its `*Implementation`, `*CompileOnly`, and `*RuntimeOnly` configurations.
answer
- implementation = compile + runtime
- compileOnly off runtime, runtimeOnly off compile
- compileClasspath/runtimeClasspath are resolvable
- buckets canBeResolved=false
- api exported via apiElements
basics
~10 sFor a source set, compileClasspath resolves from its *CompileClasspath configuration (fed by implementation + compileOnly), and runtimeClasspath from its *RuntimeClasspath configuration (fed by implementation + runtimeOnly).
solid answer
~40 sEach source set has two resolvable classpath configurations: `<set>CompileClasspath` and `<set>RuntimeClasspath`. You don't declare dependencies on those directly — they are resolvable-only. Instead you declare into the *bucket* configurations: `implementation` (visible at both compile and runtime), `compileOnly` (compile only, e.g. annotation libraries), and `runtimeOnly` (runtime only, e.g. JDBC drivers). With `java-library` you also get `api` (like `implementation` but exported to consumers). `compileClasspath` extends from `implementation` + `compileOnly`; `runtimeClasspath` extends from `implementation` + `runtimeOnly`. The `SourceSet.compileClasspath`/`runtimeClasspath` properties are `FileCollection`s pointing at those resolved configurations plus, for `test`, `main`'s output. This separation enforces that `compileOnly` deps never leak to runtime and `runtimeOnly` deps never pollute the compile API.
code
kotlin · 9 lines// Custom source set 'integrationTest' gets prefixed buckets automatically
sourceSets { create("integrationTest") }
dependencies {
"integrationTestImplementation"("org.testcontainers:testcontainers:1.19.0")
"integrationTestRuntimeOnly"("org.postgresql:postgresql:42.7.0")
}
// integrationTestCompileClasspath extends integrationTestImplementation + integrationTestCompileOnly
// integrationTestRuntimeClasspath extends integrationTestImplementation + integrationTestRuntimeOnlygo deeper
Know implementation is for both compile and runtime; recognize compileOnly and runtimeOnly exist.
Explain the extendsFrom wiring and which bucket feeds which resolvable classpath.
Discuss bucket vs. resolvable vs. consumable roles and the ABI cost of api.
Govern dependency hygiene across modules — minimizing api leakage to preserve build avoidance and clean module boundaries.
## Buckets vs. resolvable configurations Gradle splits configurations by role: - **Dependency-scope ('bucket') configurations** — where you *declare* dependencies. Examples: `implementation`, `compileOnly`, `runtimeOnly`, `api`. These are `canBeResolved = false`, `canBeConsumed = false`. - **Resolvable configurations** — what Gradle *resolves* into an actual classpath. Examples: `compileClasspath`, `runtimeClasspath`. These are `canBeResolved = true`. - **Consumable configurations** — what gets *published/exposed* to other projects, e.g. `apiElements`, `runtimeElements` (`canBeConsumed = true`). ## The wiring (for source set `main`) ``` implementation ──┐ compileOnly ──┴─► compileClasspath (resolvable) implementation ──┐ runtimeOnly ──┴─► runtimeClasspath (resolvable) ``` Concretely: `compileClasspath.extendsFrom(implementation, compileOnly)` and `runtimeClasspath.extendsFrom(implementation, runtimeOnly)`. For an arbitrary source set `foo`, the names gain the prefix: `fooImplementation`, `fooCompileClasspath`, etc. ## Why the split exists - `compileOnly` keeps things like `jakarta.annotation` or `lombok` off the runtime classpath, so they aren't shipped. - `runtimeOnly` keeps things like a JDBC driver off the compile classpath, so you can't accidentally code against the implementation. - `api` (java-library only) is like `implementation` but also lands in the consumable `apiElements`, leaking onto downstream consumers' compile classpath — use sparingly to avoid bloating the ABI. ## SourceSet view `SourceSet.getCompileClasspath()` returns a `FileCollection` that you can read or even augment (e.g. `sourceSets.test.get().compileClasspath += sourceSets.main.get().output`). The `test` set already has `main.output` folded into both classpaths by convention. ```kotlin dependencies { implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0") // compile + runtime compileOnly("org.projectlombok:lombok:1.18.32") // compile only runtimeOnly("org.postgresql:postgresql:42.7.0") // runtime only } ```
- Why can't you declare a dependency directly on `compileClasspath`?It's a resolvable-only configuration (`canBeResolved = true`, `canBeConsumed = false`) representing a resolved graph. Declaring there bypasses the bucket roles; you declare into `implementation`/`compileOnly` which it extends from.
- What's the practical difference between `api` and `implementation` for the consumer's classpath?`api` dependencies are exported via `apiElements`, so they appear on consumers' compile classpath; `implementation` dependencies stay internal and only reach consumers at runtime, shrinking the compile-time ABI and improving build avoidance.
saying these in an interview costs you the question
- Saying `compileOnly` dependencies are available at runtime — they are deliberately excluded from `runtimeClasspath`.
- Treating `compile`/`runtime` (legacy) as current — they were removed; use `implementation`/`api`/`runtimeOnly`/`compileOnly`.
- Declaring dependencies directly on `compileClasspath`/`runtimeClasspath`.