A teammate added includeBuild for a library but the consumer still downloads the old published jar from the repo. How do you diagnose and fix it?
answer
- silent fallback to repo on coord mismatch
- dependencyInsight --dependency to verify
- look for -> project annotation
- align group or add explicit rule
- no warning when includeBuild unused
basics
~10 sSubstitution silently fell back to the repo because the included build's group:name doesn't match the requested coordinates. Verify with the dependencies report, then align coordinates or add an explicit substitute(module).using(project) rule.
solid answer
~40 sComposite substitution is **silent** when it fails: if the included build doesn't publish coordinates matching the consumer's request, Gradle just resolves from the repository with no warning. Diagnose by running `./gradlew :app:dependencyInsight --dependency com.acme:widgets` — if it shows the version coordinate instead of `-> project :widgets`, substitution didn't fire. Common causes: the included build's (or its subproject's) `group` differs from what the consumer declares; the published `name`/`archivesName` differs from the project name; or the `includeBuild` path is wrong. Fix by aligning the included build's `group` to the requested coordinate, or — without touching the library — add an explicit `dependencySubstitution { substitute(module("com.acme:widgets")).using(project(":widgets")) }` to the `includeBuild` block. Then re-run the report to confirm `-> project`.
code
bash · 10 lines# Did substitution fire? Look for '-> project :widgets'
./gradlew :app:dependencyInsight --dependency com.acme:widgets
# If not, fix without editing the library: add an explicit rule
# in settings.gradle.kts:
# includeBuild("../widgets-lib") {
# dependencySubstitution {
# substitute(module("com.acme:widgets")).using(project(":widgets"))
# }
# }go deeper
Recognize that the jar still comes from the repo and something is wrong, even if you can't fully diagnose.
Use the dependencies/dependencyInsight report to confirm whether -> project appears.
Name the root cause (coordinate mismatch / silent fallback) and apply the explicit-rule or align-group fix.
Establish a convention that all internal libraries share consistent group coordinates so composites 'just work', plus CI checks that don't rely on includeBuild.
## Symptom You ran `includeBuild("../widgets-lib")` expecting your local edits to take effect, but the build still uses the stale published jar. Nothing errors — that's the trap. ## Why composites fail silently Automatic substitution requires the included build to **publish coordinates equal to** what the consumer requests (by `group:name`). If they differ by even one character, Gradle finds no match and falls through to normal repository resolution. There is **no warning** for an unused included build in this case. ## Step-by-step diagnosis 1. **Inspect resolution.** Run: ```bash ./gradlew :app:dependencyInsight --dependency com.acme:widgets ``` - `-> project :widgets` → substitution worked. - shows `com.acme:widgets:1.4.0` only → it did **not**. 2. **Check the included build's coordinates.** Look at the library's `group` (in its root or subproject `build.gradle.kts`) and its `base.archivesName`/project name. These together form the published `group:name`. 3. **Check the includeBuild path** is correct and the build actually configures (look for it in `./gradlew :app:buildEnvironment` / the configuration output). 4. **Mind the `--offline` / cache** — a previously cached jar can mask intent, but substitution overrides cache when it fires, so a cached jar means it didn't fire. ## Fixes - **Align coordinates** (preferred long-term): set the library's `group` to match the requested one so automatic substitution works for everyone. - **Explicit rule** (no library change): ```kotlin includeBuild("../widgets-lib") { dependencySubstitution { substitute(module("com.acme:widgets")).using(project(":widgets")) } } ``` - Re-run `dependencyInsight` to confirm `-> project`. ## Gotchas - A self-included build (the build can't substitute its own published module into itself) won't substitute. - Substituting a module that the included build itself depends on transitively can create loops — keep mappings minimal and explicit.
- Why doesn't Gradle warn you when an included build's substitution never matches?Automatic substitution is best-effort by design; a non-matching included build is treated as simply not relevant to that dependency, so resolution falls through to repositories without error.
- What single command tells you whether substitution happened?`./gradlew :app:dependencyInsight --dependency <group:name>` — a `-> project :x` annotation confirms substitution; a bare version coordinate means it didn't.
saying these in an interview costs you the question
- Assuming a missing error means substitution worked — it fails silently.
- Blaming the dependency cache when the real issue is mismatched group:name.
- Editing build.gradle dependency versions to 'force' the local build instead of fixing coordinates/substitution.