When automatic substitution in a composite build isn't enough, how do you declare an explicit substitution, and what is the syntax?
answer
- dependencySubstitution block on includeBuild
- substitute(module(...)).using(project(...))
- project path is inside the included build
- .using replaced deprecated .with
- fixes coordinate mismatch
basics
~10 sUse a dependencySubstitution block on the includeBuild and call substitute(module("group:name")).using(project(":path")) to map a published module to a specific project in the included build.
solid answer
~30 sAutomatic substitution covers the common case, but you need **explicit substitution** when the consumer's declared coordinates don't match what the included build publishes — for example when `group`/`name` differ, when only some configurations should substitute, or when you want to be precise about which project handles which module. You attach a `dependencySubstitution { ... }` block directly to the `includeBuild` call in `settings.gradle.kts` and write `substitute(module("com.acme:widgets")).using(project(":widgets-core"))`. The `module(...)` is the external coordinate the consumer asks for; `project(...)` is the path **within the included build**. This overrides/augments automatic detection and is also how you fix the silent-fallback problem when coordinates were mismatched.
code
kotlin · 10 lines// settings.gradle.kts of the consumer
includeBuild("../widgets-lib") {
dependencySubstitution {
// consumer asks for com.acme:widgets-api -> satisfy from local :api project
substitute(module("com.acme:widgets-api"))
.using(project(":api"))
substitute(module("com.acme:widgets-impl"))
.using(project(":impl"))
}
}go deeper
Know that an explicit block exists for when coordinates don't line up automatically.
Write the full substitute(module(...)).using(project(...)) syntax and place it on the includeBuild in settings.
Explain when explicit is required over automatic, and that the project path is included-build-relative.
Treat explicit mappings as part of a curated cross-repo dev workflow, documenting coordinate drift and migration paths.
## Why explicit substitution exists Automatic substitution only fires when the included build's published `group:name` exactly equals what the consumer requested. Real codebases drift: a library was renamed, the artifact id differs from the project name, or coordinates were never aligned. **Explicit dependency substitution** lets you state the mapping by hand. ## The DSL You attach the rule to the `includeBuild` call itself in `settings.gradle.kts`: ```kotlin includeBuild("../widgets-lib") { dependencySubstitution { substitute(module("com.acme:widgets")) .using(project(":widgets-core")) } } ``` Reading it: *"any external dependency on `com.acme:widgets` should be satisfied by the project at path `:widgets-core` inside `../widgets-lib`."* Key pieces: - `module("group:name")` — the **selector**: the external coordinate the consumer declares. You may also include a version, but for composites you usually omit it so any requested version matches. - `using(project(":path"))` — the **target**: a project **path relative to the included build's root**, not the consumer. - `.using(...)` replaced the older deprecated `.with(...)` form (removed in Gradle 8). Don't say `with`. ## When you need it - Artifact id ≠ project name (`module("com.acme:widgets-api").using(project(":api"))`). - The included build publishes under a different group than the consumer requests. - You want to substitute only a specific module while leaving others to the repository. - Migrating a multi-module library where automatic detection picks the wrong project. ## Verifying Always confirm with `./gradlew :app:dependencyInsight --dependency com.acme:widgets`; a working substitution shows `-> project :widgets-core`.
- Is the project path in `using(project(...))` relative to the consumer or the included build?Relative to the included build's root. `:api` means the `api` subproject of `../widgets-lib`, not of the consuming build.
- What's the deprecated method this replaced?`.with(...)` — replaced by `.using(...)`; the old form was removed, so on Gradle 8 you must use `using`.
- Can you omit the version in `module(...)`?Yes, and for composites you usually do, so any requested version of those coordinates is substituted to the local project.
saying these in an interview costs you the question
- Using `.with(project(...))` — deprecated and removed in Gradle 8.
- Interpreting the project path as relative to the consuming build.
- Putting the dependencySubstitution block in build.gradle instead of on the includeBuild call in settings.