Summary
This postmortem analyzes a failure where D8 crashes while dexing gradle-api-8.11.1.jar during Android instrumented tests. The issue appears only in connectedDebugAndroidTest and is caused by Gradle’s own internal Multi‑Release JAR leaking onto the androidTest runtime classpath, where D8/Desugar cannot process it correctly.
Root Cause
The underlying problem is that gradle-api-8.11.1.jar is being added to the androidTest classpath, even though it should never be visible to Android build variants.
Key contributing factors:
- A build-logic or convention plugin was previously misconfigured as an Android library, causing Android plugin classpaths to “see” Gradle’s own classloader dependencies.
- Gradle’s internal API JAR is a Multi‑Release JAR, containing:
org/gradle/internal/impldep/.../BigSignificand.classMETA-INF/versions/11/.../BigSignificand.class
- D8 treats these as duplicate classes, because D8 does not support Multi‑Release JAR semantics.
- Android test dexing includes file-based dependencies, and a misconfigured plugin or module can accidentally contribute Gradle’s internal classpath.
Why This Happens in Real Systems
This classpath leak is a classic example of Gradle classloader boundaries being broken.
Real-world causes include:
- Convention plugins implemented as Android modules, which forces the Android plugin to resolve their dependencies as if they were runtime libraries.
- Using
implementation(files(...))orcompileOnly(files(...))inside build-logic, which unintentionally exposes Gradle’s own classpath. - Misconfigured composite builds, where build-logic modules are treated as part of the main project graph.
- AGP’s androidTest classpath being more permissive, pulling in additional artifacts for instrumentation.
Real-World Impact
When Gradle internal JARs leak into Android build variants, you get:
- D8 crashes due to unsupported JAR formats (Multi‑Release JARs).
- Desugar failures when encountering Java 11+ bytecode in META-INF/versions.
- Non-reproducible builds, because the issue appears only in androidTest tasks.
- Long debugging cycles, since
dependencyInsightdoes not show file-based or plugin-sourced classpath entries.
Example or Code (if necessary and relevant)
A typical misconfiguration looks like this:
// INCORRECT: build-logic plugin implemented as Android library
plugins {
id("com.android.library")
kotlin("android")
}
Correct approach:
// CORRECT: build-logic should be a JVM Gradle plugin
plugins {
`kotlin-dsl`
}
How Senior Engineers Fix It
Experienced engineers approach this by isolating classpaths and removing Gradle internals from Android variants.
Typical fixes:
- Ensure all build-logic modules use
kotlin-dsl, not Android plugins. - Verify no plugin applies Android plugins transitively.
- Search for file-based dependencies:
grep -R "files(" -n build-logic/grep -R "gradle-api" -n
- Check for accidental plugin application in
build.gradle.kts:apply(plugin = "com.android.library")in non-Android modules.
- Inspect the resolved classpath for androidTest:
./gradlew :core:database:dependencies --configuration debugAndroidTestRuntimeClasspath
- Remove any custom classpath manipulation in build-logic:
- No
configurations.all { ... }that adds files - No
buildscript.dependencies { classpath(files(...)) }
- No
- Rebuild Gradle caches to ensure no stale generated JARs remain:
rm -rf ~/.gradle/caches/8.11.1/generated-gradle-jars
Why Juniors Miss It
Less experienced engineers often overlook this because:
- Gradle classloader boundaries are invisible, and leaks are non-obvious.
dependencyInsightdoes not show file-based or plugin-sourced dependencies, leading to false assumptions.- androidTest classpaths behave differently, making the issue appear random.
- Multi‑Release JAR behavior is poorly documented, and D8’s limitations are not widely known.
- Build-logic misconfigurations rarely fail immediately, so the root cause is far removed from the symptom.
If you want, I can walk through how to identify exactly which module or plugin is leaking gradle-api into your androidTest classpath.