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?
answer
- project(':lib') not a coordinate
- logical path from settings, not filesystem
- implicit task dependency — builds first
- consumes apiElements/runtimeElements
- no publish, incremental
basics
~10 sUse 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.
solid answer
~30 sWithin a multi-project build you declare a **project dependency** with the `project(':path')` notation: `implementation(project(':lib'))`. The colon-separated path is the logical project path (from `settings.gradle(.kts)`), not a filesystem path. Unlike an external dependency — declared as a `group:name:version` coordinate that Gradle resolves from a repository — a project dependency points at a sibling subproject in the same build. Gradle builds that subproject first (it becomes a task dependency), then consumes its produced artifact and its transitive dependencies through the producer's `apiElements`/`runtimeElements` outgoing configurations. This gives you incremental, source-aware builds: changing `:lib` rebuilds and re-tests dependents without publishing anything.
code
kotlin · 8 lines// settings.gradle.kts
include(":app", ":lib")
// app/build.gradle.kts
dependencies {
implementation(project(":lib")) // local subproject
implementation("com.google.guava:guava:33.0.0-jre") // external module
}go deeper
Know the project(':lib') notation and that it points at a sibling module, not a downloaded artifact.
Explain the implicit task dependency, transitive api propagation, and the path-is-logical-not-filesystem distinction.
Discuss variant-aware wiring via apiElements/runtimeElements and how conflicts resolve in one graph.
Frame when to keep code in-build as project deps vs. extracting to published libraries / composite builds for org-scale modularization.
## What a project dependency is A Gradle build can contain many **subprojects** (also called modules or projects), each declared in `settings.gradle(.kts)` via `include(":app", ":lib")`. The leading colon and colons separating segments form a **logical project path** (`:lib`, `:libs:core`), independent of where the directory actually lives on disk. When one subproject needs another, you declare a **project dependency**: ```kotlin dependencies { implementation(project(":lib")) } ``` ## How it differs from an external dependency - **External / module dependency**: `implementation("com.google.guava:guava:33.0.0-jre")` — a `group:name:version` coordinate that Gradle **resolves** from a declared repository (Maven Central, etc.), downloading a published artifact and its POM/module metadata. - **Project dependency**: `project(":lib")` — points at a subproject in the *same build*. Nothing is downloaded; Gradle adds an implicit **task dependency** so `:lib` is assembled before its consumer, then wires the consumer's classpath to `:lib`'s outgoing artifacts. ## What gets wired under the hood Gradle uses **variant-aware** resolution. The producer (`:lib`) exposes *consumable* configurations such as `apiElements` (compile classpath) and `runtimeElements` (runtime classpath). The consumer's *resolvable* configurations (`compileClasspath`, `runtimeClasspath`) select the matching variant by attributes (e.g. `Usage`, `LibraryElements`). So `implementation(project(":lib"))` transitively brings in whatever `:lib` declared as `api` dependencies and the appropriate classes/jar. ## Practical consequences - **Incremental builds**: editing `:lib` triggers recompilation of dependents — no publish step. - **Up-to-date checks & build cache** work across the boundary. - The dependency participates in the same **dependency graph**, so version conflicts between `:lib`'s transitive deps and yours are resolved together. Use project dependencies for code you control inside the build; use coordinates for third-party or already-published artifacts.
- Is `:lib` a filesystem path?No — it's a logical project path derived from `settings.gradle(.kts)`. The directory can be remapped with `project(":lib").projectDir = file("...")`.
- Does Gradle publish `:lib` to a repository to make this work?No. It wires the consumer directly to the producer's outgoing artifacts in-build and adds a task dependency so `:lib` is built first.
An external dependency is ordering a part from a catalog; a project dependency is grabbing a part your own factory builds on the next bench — no shipping, and if you change the blueprint it's remade on the spot.
saying these in an interview costs you the question
- Claiming `project(':lib')` is a path on disk.
- Saying it requires publishing to mavenLocal first.
- Confusing it with `includeBuild` (that's composite builds across separate builds).