Your App is 10MB Heavier Because of a Library You Didn't Even Know You Had
A Forensic Guide to Auditing Transitive Dependencies, Pruning DEX Bloat, and Fixing Gradle Version Conflicts.
Imagine you’re packing for a weekend trip. You pack a suitcase, but unbeknownst to you, every item you put in brings along its “friends.” You pack a toothbrush; it brings a tube of toothpaste, a rinsing cup, and a towel. By the time you reach the airport, your carry-on is overweight, and you’re paying extra baggage fees for things you never intended to use.
This is exactly what happens in Transitive Dependency Hell.
When you add a single line to your build.gradle.kts, you aren't just adding one library. You are inviting a family tree of dependencies into your project. If left unaudited, these "ghost" libraries inflate your DEX count, slow down build times, and cause runtime crashes when two libraries disagree on which version of a shared friend they want to hang out with.
The Result: After removing just three unused transitive dependencies and aligning OkHttp versions, one production app I worked on saw a 7.4 MB reduction in APK size and an 18% drop in DEX count — with zero functional changes.
The Ghost in the Machine: What is a Transitive Dependency?
A Direct Dependency is one you explicitly declare, like implementation("com.squareup.retrofit2:retrofit:2.9.0").
A Transitive Dependency is a library that Retrofit needs to function (like OkHttp). You didn’t ask for it, but you got it.
The Real-World Crash: The Glide vs. SDK Trap
A common scenario: You switch from Glide to Coil to save size. You remove the Glide line from Gradle, but the app crashes with a NoClassDefFoundError. Why? Because a third-party SDK (like a Customer Support chat tool) was secretly relying on your project to provide a specific version of Glide. By removing your direct declaration, the version the SDK required may have been omitted or changed via conflict resolution to a version that lacks a specific method.
Step 1: Visualizing the Tree of Chaos
To cut the fat, you have to see it. Android Studio’s “External Libraries” list is too noisy. Instead, use the Gradle terminal to see what actually ships in your final artifact.
The Power Command
Run this to see the tree for your production build:
./gradlew app:dependencies --configuration releaseRuntimeClasspath > deps.txtHow to read deps.txt:
- The
(*): This means the dependency is repeated elsewhere and Gradle has omitted the children to save space. - The
->: This shows version resolution. By default, Gradle picks the highest version requested in the graph. - Note on BOMs: If you use a Bill of Materials (like Firebase or Compose BOM), version resolution is governed by constraints rather than “highest-version wins.”
Step 2: The Scalpel — Using exclude
If you discover a library is pulling in an ancient version of a library or a duplicate you already have, you can use the exclude rule to prune the graph.
Kotlin DSL Example (build.gradle.kts)
dependencies {
implementation("com.datacrunch.android:analytics-sdk:5.2.0") {
// Exclude the SDK's version of OkHttp to prevent version conflict
exclude(group = "com.squareup.okhttp3", module = "okhttp")
}
// Explicitly provide the version your app actually uses
implementation("com.squareup.okhttp3:okhttp:4.11.0")
}Note: Use this with caution. If the parent SDK calls a method that only exists in its specific version of OkHttp, you will face a
NoSuchMethodErrorat runtime.
Step 3: Automation — The Dependency Analysis Plugin
Manually checking a 2,000-line text file is a chore. The Dependency Analysis Gradle Plugin is the gold standard for this. It identifies:
- Unused dependencies: Libraries you declared but never actually used.
- Used transitive dependencies: Libraries you use in code but “stole” from another library’s declaration.
Setup:
Add to your root build.gradle.kts:
plugins {
id("com.autonomousapps.dependency-analysis") version "1.28.0"
}Run ./gradlew buildHealth to get a report of exactly what to delete or move to runtimeOnly.
Step 4: Constraints and Resolution Strategies
Sometimes exclude is too tedious. You can use a dependencySubstitution to force a specific version across the entire project, ensuring "one version to rule them all."
configurations.all {
resolutionStrategy {
dependencySubstitution {
substitute(module("org.jetbrains.kotlin:kotlin-stdlib"))
.using(module("org.jetbrains.kotlin:kotlin-stdlib:1.9.20"))
}
}
}🔍 The Audit Checklist: Red Flags
Keep an eye out for these signs of dependency bloat in your deps.txt:
- Multiple image loaders: Seeing Glide, Coil, and Picasso in the same graph.
- Analytics Bloat: SDKs pulling in their own massive networking stacks (like an old version of Volley).
- Fragmented AndroidX: Different versions of the same
androidxartifact causing compatibility headaches. - Test Leaks: Test-only libraries appearing in the
releaseRuntimeClasspath.
Important Note: While R8/ProGuard can remove unused classes, it often cannot remove entire libraries that are referenced transitively. Dependency hygiene is your first line of defense.
🙋♂️ Frequently Asked Questions (FAQs)
Will removing a transitive dependency make my app faster?
Yes. It reduces the DEX count (the code the OS loads) and the runtime memory footprint, which can improve startup performance and stability on low-end devices.
How do I know if a library is safe to exclude?
There is no magic button. Exclude it, clean the project, and perform a smoke test on the features that rely on the parent library. Look for NoSuchMethodError or ClassNotFoundException.
Does this matter for App Bundles (AAB)?
Absolutely. While AABs optimize resources (images/languages) for specific devices, the compiled code (DEX) is usually delivered in full. Unnecessary dependencies increase the download size for everyone.
💬 A Question for the Readers
What is the “ghost” library that bloated your app the most? Have you ever found a library pulling in something absurd (like an entire testing framework or an old version of JUnit) into your release build? Let’s swap horror stories in the comments.
Video References
- Tips for Reducing APK Size in Production — Practical strategies for auditing production bloat.
📘 Master Your Next Technical Interview
Since Java is the foundation of Android development, mastering DSA is essential. I highly recommend “Mastering Data Structures & Algorithms in Java”. It’s a focused roadmap covering 100+ coding challenges to help you ace your technical rounds.
- E-book (Best Value! 🚀): $1.99 on Google Play
- Kindle Edition: $3.49 on Amazon
- Also available in Paperback & Hardcover.

Comments
Post a Comment