What is the DependencyHandler.add(configuration, notation) API, and how does it relate to the `implementation(...)` DSL methods?
answer
- add(configurationName, notation)
- DSL methods = generated sugar over add
- per-configuration method auto-created
- use add for dynamic/computed config names
- 3-arg overload for configuring closure
basics
~10 sadd(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.
solid answer
~40 sInside `dependencies { }` you're operating on a **`DependencyHandler`**. Its general method is `add(String configurationName, Object dependencyNotation)` — it takes the target configuration's name and any supported notation (string `g:n:v`, a map, a project, files, a platform, etc.), creates the corresponding `Dependency` object, and registers it on that configuration. The familiar `implementation("g:n:v")`, `testImplementation(...)`, `api(...)` calls are **dynamically-provided convenience methods**: each configuration contributes a method of the same name that delegates to `add("<thatConfiguration>", notation)`. You reach for `add` directly when the configuration name is computed/dynamic, when you iterate over configurations programmatically, or in plugin code where the typed DSL method may not exist. There's also an overload `add(configuration, notation, configureClosure)` to set per-dependency options. So the DSL methods are sugar; `add` is the API they're sugar over.
code
kotlin · 9 linesdependencies {
// these two are equivalent
implementation("com.google.guava:guava:33.0.0-jre")
add("implementation", "com.google.guava:guava:33.0.0-jre")
// useful when the configuration name is computed
val cfg = "testImplementation"
add(cfg, "org.assertj:assertj-core:3.25.3")
}go deeper
Just recognize that implementation(...) adds a dependency to the implementation configuration.
Explain that add(configuration, notation) is the underlying API and that DSL methods are generated per configuration as sugar over it.
Show the dynamic/looping use cases and the three-arg overload; tie it to how custom configurations gain their own DSL method.
Discuss plugin-authoring patterns, programmatic dependency wiring across configurations, and keeping such logic readable/maintainable in a shared build platform.
## The object behind `dependencies { }` The `dependencies { }` block configures a project's **`DependencyHandler`**. Every way of declaring a dependency ultimately funnels through it. ## The core method ``` Dependency add(String configurationName, Object dependencyNotation) Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure) ``` - **`configurationName`** — the name of the configuration to attach to (`"implementation"`, `"api"`, `"testRuntimeOnly"`, or a custom one you created). - **`dependencyNotation`** — any supported notation: a `"g:n:v"` string, a map, the result of `project(":other")`, `files(...)`, `platform(...)`, etc. - It builds the matching `Dependency` instance and adds it to that configuration, returning the created dependency. ## Why the DSL methods feel different When a plugin creates a configuration (the `java` plugin creates `implementation`, `api`, `compileOnly`, `runtimeOnly`, `testImplementation`, …), Gradle exposes a **same-named method** on the `DependencyHandler` so you can write: ```kotlin dependencies { implementation("com.google.guava:guava:33.0.0-jre") } ``` instead of: ```kotlin dependencies { add("implementation", "com.google.guava:guava:33.0.0-jre") } ``` Both lines are equivalent — the first is generated sugar over the second. ## When to call `add` directly - **Dynamic configuration names** — the target is computed at runtime: ```kotlin val cfg = if (useNative) "runtimeOnly" else "implementation" dependencies { add(cfg, "io.netty:netty-transport-native-epoll:4.1.107.Final") } ``` - **Iterating/programmatic registration** — e.g. a loop or plugin adding the same dependency to several configurations. - **Plugin authoring** — where you don't have (or don't want to rely on) the typed accessor. ## With a configuring block The three-arg overload lets you set options on the created dependency: ```kotlin dependencies { add("implementation", "org.hibernate:hibernate-core:6.4.4.Final") { this as ExternalModuleDependency isTransitive = false } } ``` ## Mental model Think of `add` as the single registration primitive and the per-configuration methods (`implementation`, `api`, …) as thin, ergonomic aliases generated per configuration. Knowing this demystifies how custom configurations get their own DSL method and unlocks dynamic/looping declaration patterns.
- Where does the `implementation(...)` method come from if it isn't hand-written in the API?It's contributed dynamically when the configuration `implementation` is created (by the java/java-library plugin); Gradle exposes a same-named method on the DependencyHandler that delegates to `add("implementation", ...)`.
- When is calling `add` directly clearly better than the DSL method?When the configuration name is computed at runtime, when looping to register dependencies across multiple configurations, or in plugin code that can't rely on a typed accessor.
saying these in an interview costs you the question
- Believing `implementation` is a special hard-coded keyword rather than a per-configuration generated method over `add`.
- Thinking `add` and the DSL methods produce different resolution behavior.