D8 fails while dexing gradle-api-8.11.1.jar during Android instrumented tests

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.class
    • META-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(...)) or compileOnly(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 dependencyInsight does 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(...)) }
  • 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.
  • dependencyInsight does 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.

Leave a Comment