skip to content

Declaring Dependencies

How dependencies are declared: which configuration they go in, what those configurations are allowed to do, and the notations available. Interviewers start here because api versus implementation is the single most-asked Gradle question.

on this pageshow

explore

questions

30

You try to resolve `implementation` and get an error that it cannot be resolved. Why, and what should you resolve instead?

level: juniorimportance: must knowfreq 50%

answer

  1. implementation = bucket, not resolvable
  2. isCanBeResolved = false
  3. resolve compileClasspath / runtimeClasspath
  4. extendsFrom inherits the bucket
  5. declare vs resolve roles

basics

~10 s

implementation is a declaration bucket — isCanBeResolved is false — so it has no resolution result. Resolve compileClasspath or runtimeClasspath, which extend from it.

solid answer

~40 s

`implementation` is a **dependency-scope bucket**: both `isCanBeResolved` and `isCanBeConsumed` are false. Its only job is to *hold* declared dependencies, not to produce a file set, so asking for its resolved artifacts errors with 'Resolving configuration 'implementation' directly is not allowed'. The configurations you actually resolve are `compileClasspath` and `runtimeClasspath`, which are resolvable and `extendsFrom` the relevant buckets (`implementation`, `compileOnly`/`runtimeOnly`, `api`). So if you need the files, ask the right resolvable configuration — e.g. `configurations.runtimeClasspath.get().resolve()` or wire a task input to it. This protects you from accidentally resolving a half-defined set and from the classic mistake of treating one configuration as both the place you declare and the thing you resolve.

code

kotlin · 5 lines
kotlin
// WRONG: implementation is a bucket
// configurations.implementation.get().resolve()  // ERROR

// RIGHT: resolve the resolvable sibling
val runtimeFiles = configurations.runtimeClasspath.get().resolve()

go deeper

for a junior

Recognize that buckets can't be resolved and name compileClasspath/runtimeClasspath as the right targets.

for a middle

Explain the flag mechanics and the extendsFrom inheritance that feeds the classpath.

for a senior

Push toward lazy wiring (Provider as task input) instead of eager .resolve(); tie to configuration cache.

for a principal

Use this to justify role discipline in convention plugins so build authors never resolve buckets across the org.

## The error Gradle refuses `configurations.implementation.get().resolve()` (or `.files`) with a message like *'Resolving configuration 'implementation' directly is not allowed'*. This is by design. ## Why Every configuration has two booleans: - `isCanBeResolved` — may Gradle compute artifacts for it? - `isCanBeConsumed` — may other projects depend on it? A **bucket** (dependency-scope) like `implementation` has *both false*. It exists only so you can write `dependencies { implementation("...") }`. Resolving means running dependency resolution + variant selection to get concrete files — a bucket deliberately has no such result. ## What to resolve instead The Java plugin creates resolvable configurations that inherit the buckets: - `compileClasspath` — resolvable; `extendsFrom(implementation, compileOnly, api, ...)`. - `runtimeClasspath` — resolvable; `extendsFrom(implementation, runtimeOnly, api, ...)`. So declare in the bucket, resolve the classpath: ```kotlin val files = configurations.runtimeClasspath.get().resolve() // OK ``` ## Wiring into tasks (preferred) Rather than calling `.resolve()` eagerly, wire the resolvable configuration as a task input so resolution is lazy and cache-friendly: ```kotlin tasks.register("listRuntime") { val cp = configurations.runtimeClasspath doLast { cp.get().forEach { println(it.name) } } } ``` ## Takeaway 'Cannot be resolved' is not a bug — it tells you that you reached for the *declaration* role when you wanted the *resolution* role. Pick the resolvable sibling.

  • How does `runtimeClasspath` get the dependencies you declared in `implementation`?
    Through `extendsFrom`: `runtimeClasspath` extends `implementation` (and `runtimeOnly`, `api`, etc.), so declarations in the bucket flow up into the resolvable configuration.
  • Is there a downside to calling `.resolve()` directly in configuration scripts?
    Yes — it triggers eager resolution at configuration time, hurting performance and configuration cache. Prefer wiring the configuration (a Provider) as a lazy task input.

saying these in an interview costs you the question

  • Claiming the error is a Gradle bug or a missing dependency.
  • Trying to flip `isCanBeResolved = true` on `implementation` to 'fix' it instead of resolving the right configuration.

context

open as a page

What is the `dependencies {}` block in a Gradle build script, and how do you add a dependency to a specific configuration inside it?

level: juniorimportance: must knowfreq 70%

basics

~10 s

The dependencies {} block is where you declare a module's dependencies. Inside it you call a configuration name like implementation(...) with the dependency coordinates (group:name:version) to attach a dependency to that configuration.

open as a page

How do `testImplementation` and `testRuntimeOnly` relate to the main source set's configurations, and when do you use each?

level: juniorimportance: must knowfreq 58%

basics

~20 s

testImplementation adds a dependency to the test compile + runtime classpath (e.g. JUnit, Mockito). testRuntimeOnly adds it only to the test runtime classpath (e.g. the JUnit Platform launcher engine). Test classpaths also extend the main ones.

open as a page

How do you exclude a single transitive dependency that is pulled in by one specific dependency in Gradle?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Attach an exclude block to that one dependency, naming the unwanted artifact by group and/or module. Only that dependency's transitive graph is affected.

open as a page

What does the string dependency notation 'group:name:version' mean in Gradle, and how do you declare such a dependency?

level: juniorimportance: must knowfreq 80%

basics

~10 s

It's the shorthand for a module's coordinates: group (org), name (artifact), version. You declare it inside a configuration, e.g. implementation("org.apache.commons:commons-lang3:3.14.0").

open as a page

How do you declare a dependency on another module within the same Gradle multi-project build, and how does it differ from depending on an external library?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Use project(':lib') instead of a coordinate string, e.g. implementation(project(':lib')). Gradle wires the local subproject directly rather than resolving it from a remote repository.

open as a page

What are test fixtures in Gradle, and how do you enable them in a project?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Test fixtures are reusable test helper code (builders, fakes, sample data) shared across test classes and other projects. Enable them by applying the java-test-fixtures plugin, then put code in src/testFixtures/java.

open as a page

What are the three roles a Gradle configuration can play, and what does each one do?

level: middleimportance: must knowfreq 55%

basics

~10 s

A configuration can be a dependency-scope (bucket) that collects declared dependencies, resolvable (you ask it to compute a classpath), or consumable (exposed as a variant to other projects).

open as a page

Explain when you would use `compileOnly` versus `runtimeOnly` when declaring a dependency, and give a realistic example of each.

level: middleimportance: must knowfreq 62%

basics

~20 s

compileOnly puts a dependency on the compile classpath but not runtime (e.g. annotation libraries, provided-by-container APIs). runtimeOnly puts it on the runtime classpath but not compile (e.g. a JDBC driver or SLF4J binding loaded by reflection).

open as a page

What is the difference between the `implementation` and `api` dependency configurations in a Gradle java-library project, and when would you choose one over the other?

level: middleimportance: must knowfreq 78%

basics

~10 s

api exposes a dependency on the consumer's compile classpath (it leaks); implementation keeps it internal to the module. Use api only when the dependency appears in your public types, otherwise implementation.

open as a page

When and why would you use Gradle's map dependency notation instead of the string form, and what keys does it support?

level: middleimportance: must knowfreq 60%

basics

~20 s

Map notation spells out each coordinate as a key: group:, name:, version:, plus classifier: and ext:. Use it when you need a classifier/extension or to set a segment programmatically — string notation can't express those.

open as a page

How do you consume test fixtures from another project in the same build, and how does it differ from consuming published external fixtures?

level: middleimportance: must knowfreq 50%

basics

~10 s

Use the testFixtures() helper inside a dependency declaration: testImplementation(testFixtures(project(":lib"))) for a local project, or testImplementation(testFixtures("g:a:v")) for an external module. External requires the producer to publish Gradle Module Metadata.

open as a page

Explain `extendsFrom` and walk through how a declaration in `api` ends up on a downstream consumer's compile classpath.

level: middleimportance: should knowfreq 30%

basics

~10 s

extendsFrom makes one configuration inherit the dependencies declared in another. api is included in both apiElements (exposed) and compileClasspath, so its dependencies propagate to consumers' compile classpaths.

open as a page

When would you use `configurations.all { exclude(...) }` instead of a per-dependency exclude, and what are the trade-offs?

level: middleimportance: should knowfreq 55%

basics

~10 s

Use a configuration-wide exclude when an unwanted artifact arrives through many dependency paths. It removes that coordinate from every dependency in the configuration at once, but it's a blunt, global rule.

open as a page

What does `isTransitive = false` do on a dependency, and how does it differ from excludes?

level: middleimportance: should knowfreq 45%

basics

~10 s

Setting isTransitive = false makes Gradle bring in only that one artifact and none of its declared dependencies. Excludes prune specific coordinates; isTransitive = false drops the entire transitive subtree at once.

open as a page

What is the DependencyHandler.add(configuration, notation) API, and how does it relate to the `implementation(...)` DSL methods?

level: middleimportance: should knowfreq 45%

basics

~10 s

add(configuration, notation) is the underlying DependencyHandler method that attaches a dependency to a named configuration. The implementation(...), api(...) etc. DSL calls are just convenience wrappers that call add with that configuration name.

open as a page

When and how would you declare a dependency on local JARs using `files()` and `fileTree()`, and what are the trade-offs versus repository-resolved dependencies?

level: middleimportance: should knowfreq 55%

basics

~10 s

Use implementation(files("libs/foo.jar")) for a specific JAR, or implementation(fileTree("libs") { include("*.jar") }) for a folder of JARs. They reference local files directly with no version metadata or transitive resolution.

open as a page

Explain the difference between testFixturesApi and testFixturesImplementation. When should a fixture dependency go in each?

level: middleimportance: should knowfreq 38%

basics

~20 s

testFixturesApi dependencies are part of the fixtures' public API and leak onto consumers' compile classpath; testFixturesImplementation ones are internal to the fixtures and only reach consumers at runtime. Put a library in Api only if its types appear in fixture signatures.

open as a page

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

level: seniorimportance: should knowfreq 35%

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.

open as a page

What do `isCanBeResolved` and `isCanBeConsumed` mean, and what is wrong with a configuration where both are true?

level: seniorimportance: should knowfreq 28%

basics

~10 s

isCanBeResolved = Gradle can compute its artifacts for this build; isCanBeConsumed = other projects can depend on it. Both true is the deprecated legacy 'do-everything' role and should be split.

open as a page

A consumer of your library suddenly can no longer compile after you changed a dependency from `api` to `implementation`. What happened, and how do you reason about and fix it correctly?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The consumer was relying on a transitively leaked compile dependency. Moving it from api to implementation removed it from the consumer's compile classpath. The right fix is for the consumer to declare its own direct dependency, since it actually uses those types.

open as a page

Excludes are often a code smell. When are they justified, and what modern Gradle mechanisms should you prefer for managing the transitive graph?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Excludes are justified for genuinely unwanted artifacts (duplicate log backends, broken metadata). For version conflicts prefer constraints/platforms; for competing implementations of the same capability prefer capability resolution. Reserve excludes for true removal.

open as a page

Within a single dependency declaration, how do you turn off transitive resolution or exclude a specific transitive module, and what's the difference between the two?

level: seniorimportance: should knowfreq 50%

basics

~10 s

Pass a configuring block to the declaration. isTransitive = false drops ALL transitive dependencies of that module; exclude(group = ..., module = ...) removes only a specific transitive module while keeping the rest.

open as a page

A teammate wired a project dependency `project(':lib')` but the other module lives in a separate Gradle build. Why does this fail, and what's the correct mechanism?

level: seniorimportance: should knowfreq 35%

basics

~10 s

project(':lib') only resolves subprojects included in the same build's settings.gradle(.kts). A module in a separate build isn't a subproject, so the path is unknown. Use includeBuild (a composite build) to substitute it.

open as a page

How do you depend on a specific producer configuration of another subproject, e.g. `project(path: ':lib', configuration: 'myConfig')`, and when is that useful?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Use the map notation project(path: ':lib', configuration: 'instrumented') (Groovy) or project(":lib", configuration = "instrumented") (Kotlin) to consume a named outgoing configuration of the producer instead of its default variant.

open as a page

Why might you prefer Gradle's java-test-fixtures over a hand-rolled shared test-utilities module? What are the trade-offs?

level: seniorimportance: should knowfreq 30%

basics

~20 s

java-test-fixtures keeps fixtures inside the module they support, ships them as a separate opt-in variant (not on production classpaths), and requires no extra module. A shared 'test-utils' module is simpler to grasp but scatters helpers away from their owner and risks leaking onto runtime.

open as a page

What is the purpose of `because("reason")` on a dependency or exclude, and where does that reason surface?

level: juniorimportance: nice to knowfreq 25%

basics

~10 s

because(...) attaches a human-readable justification to a dependency or constraint. It documents why the choice was made and shows up in dependency-insight reports, helping future maintainers understand the graph.

open as a page

Explain the extended string notation with a classifier and extension, e.g. `group:name:version:classifier@ext`. When is it needed?

level: middleimportance: nice to knowfreq 30%

basics

~10 s

Append :classifier to add a classifier and @ext to set a non-default extension: "io.netty:netty...:4.1.107.Final:linux-x86_64" or "group:name:1.0@zip". Needed for native/platform artifacts, sources/javadoc, or non-jar packaging.

open as a page

What are `gradleApi()` and `localGroovy()`, and where would you declare them?

level: middleimportance: nice to knowfreq 25%

basics

~10 s

They're dependency notations for building Gradle plugins. gradleApi() puts the Gradle API on the compile classpath; localGroovy() adds the Groovy version bundled with the running Gradle. You declare them in a plugin/buildSrc project's dependencies.

open as a page

What do you need to publish and consume test fixtures of an external library, and what common pitfalls arise?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

The producer must apply java-test-fixtures, use maven-publish, and publish Gradle Module Metadata (the .module file) so the fixtures variant is described. Consumers then use testFixtures("g:a:v"). Common pitfalls: metadata not published, or the producer thinking a POM is enough.

open as a page