skip to content

Dependency Configurations

The standard configurations — api, implementation, compileOnly, runtimeOnly and their test counterparts — and how api leaks onto a consumer's compile classpath while implementation does not. The canonical Gradle dependency question.

on this pageshow

questions

5

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%

answer

  1. dependencies {} = where deps are declared
  2. line = configurationName(notation)
  3. config name chooses classpath
  4. plugins create the config methods
  5. Kotlin parens+quotes; Groovy terser

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.

solid answer

~40 s

The `dependencies {}` block in `build.gradle(.kts)` is the DSL where you declare which external modules (or projects) your module needs. Inside it, each line is a method call whose **name is the configuration** and whose **argument is the dependency notation**. So `implementation("com.google.guava:guava:33.0.0-jre")` adds Guava to the `implementation` configuration. The configuration determines *which classpath(s)* the dependency lands on — `implementation` (compile + runtime), `compileOnly`, `runtimeOnly`, `testImplementation`, etc. The plugins you apply create the available configurations: the `java` plugin adds `implementation`/`compileOnly`/`runtimeOnly`/`testImplementation`/`testRuntimeOnly`, and `java-library` additionally adds `api`. In Kotlin DSL the calls look like function invocations with parentheses; in Groovy DSL you can omit parentheses (`implementation 'group:name:version'`).

code

kotlin · 8 lines
kotlin
plugins { `java-library` }

dependencies {
    api("com.google.guava:guava:33.0.0-jre")
    implementation("org.slf4j:slf4j-api:2.0.13")
    runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
}

go deeper

for a junior

Know it's the block where you list dependencies and that each line is configuration("group:name:version").

for a middle

Explain that plugins create the configurations and the name selects the target classpath; note Kotlin vs Groovy DSL differences.

for a senior

Discuss how the available configuration set derives from the plugin stack and the implications of choosing each.

for a principal

Frame conventions for consistent dependency declaration (DSL choice, configuration discipline) across a large build.

## What the block is Every Gradle module (subproject) has a `build.gradle` (Groovy) or `build.gradle.kts` (Kotlin) script. The `dependencies {}` block is the standard place to declare dependencies. It's a configuration block on the project; inside it, the receiver exposes a method per **configuration**. ## The shape of a declaration ``` <configurationName>(<dependency notation>) ``` - **configurationName** — e.g. `implementation`, `api`, `compileOnly`, `runtimeOnly`, `testImplementation`. This is literally a method created by the applied plugins. If you call a configuration that doesn't exist (e.g. `api` without `java-library`), the build fails with an unknown-method error. - **dependency notation** — most commonly the `group:name:version` string coordinate. (Other notations like `project(...)`, files, and maps exist but belong to neighboring topics.) ## Kotlin vs Groovy DSL ```kotlin // build.gradle.kts (Kotlin DSL) plugins { `java-library` } dependencies { api("com.google.guava:guava:33.0.0-jre") implementation("org.slf4j:slf4j-api:2.0.13") testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") } ``` ```groovy // build.gradle (Groovy DSL) plugins { id 'java-library' } dependencies { api 'com.google.guava:guava:33.0.0-jre' implementation 'org.slf4j:slf4j-api:2.0.13' testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' } ``` Kotlin DSL requires parentheses and double quotes and gives you type-safe accessors + IDE completion for configuration names. Groovy DSL allows the terser space-separated, single-quoted form. ## Plugins create configurations The block is empty of meaningful methods until a plugin contributes configurations: - `java` → `implementation`, `compileOnly`, `runtimeOnly`, `annotationProcessor`, `testImplementation`, `testCompileOnly`, `testRuntimeOnly`, … - `java-library` → all of the above **plus** `api`, `compileOnlyApi`. So the available configuration names are a function of your plugin set. Choosing the right configuration name is choosing which classpath(s) the dependency affects — that's the substance of declaring dependencies.

  • Why might calling `api(...)` in the dependencies block fail?
    Because `api` is only created by the `java-library` plugin. Without it applied, `api` is an unknown method and the build fails.
  • What does the configuration name in each line actually control?
    Which classpath(s) the dependency lands on — compile, runtime, test, etc. — and, for `api` vs `implementation`, whether it's exposed transitively to consumers.

saying these in an interview costs you the question

  • Thinking the configuration name is arbitrary — it must be a real configuration created by an applied plugin.
  • Assuming all configuration names exist by default (e.g. `api` without `java-library`).
  • Mixing up Kotlin DSL (parentheses required) and Groovy DSL syntax.

context

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

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

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