◉ANTIGRAVITY LABJP
Articles/Antigravity Basics
◉ Antigravity Basics/2026-10-20Intermediate

NoClassDefFoundError on Old Android Devices Only — Four Clues I Gather Before Handing It to an Agent

Everything works on new phones, but Android 6 devices crash. Using a Supplier NoClassDefFoundError that appeared after a Glide update, this note covers the order of diagnosis, the coreLibraryDesugaring setup, and what to prepare before asking an agent to investigate.

antigravity468android31desugaringcrashlytics6troubleshooting119

One morning I opened Crashlytics and found a single new line in the stack traces: NoClassDefFoundError: java.util.function.Supplier. It did not reproduce on my emulator, and when I filtered by device, nearly every affected user was on Android 6.0.1.

As an indie developer who has kept Android apps running for a long time, I flinch whenever a crash shows up right after a library update. My first thought is always, "Which of my own lines did this?" This time I had not touched my own code at all.

What I want to say up front is that the cause lived in the gap between what newer devices provide and what old ones do, and the fix was one line of build configuration. Below is the order I used to get there, plus the clues I now prepare before asking an agent to help.

Reading the symptom: old OS only, not at launch, not my class

The error text alone narrows things down a lot.

ClueValue in this caseWhat it suggests
Exception typeNoClassDefFoundErrorThe class existed at compile time but is missing on the device
Missing classjava.util.function.SupplierA Java 8 API; on Android it only ships with API 24 (7.0) and later
Affected devicesAndroid 6.0.1 (API 23)Only devices below API 24 crash
Top of the stackA dependency's packageMy code did not call it directly; a library uses it internally

With those four rows, the leading hypothesis is that a library started using a Java 8 API while the app never told the build to substitute it for old OS versions. If the top frame is in your own package, or the error is a ClassNotFoundException, look elsewhere.

Diagnosis order, cheapest first

  1. Check minSdk. Below 24, you cannot rely on the OS for Java 8 standard APIs.
  2. Check whether desugaring is enabled. If coreLibraryDesugaringEnabled is absent, that is the gap.
  3. Find which dependency references Supplier. Confirm that the library you just updated is the one, by looking at bytecode.
  4. Reproduce on an old OS before and after the fix.

Step 3 fits in a small script. Class files keep their constant-pool strings as plain text, so you can grep a jar without unpacking it.

#!/usr/bin/env bash
# What this solves: list dependency jars that reference java.util.function.Supplier
# Usage: ./find-supplier-users.sh path/to/jars/*.jar
set -euo pipefail
 
for jar in "$@"; do
  # Concatenate the class files and count occurrences of the reference string
  hits=$(unzip -p "$jar" '*.class' 2>/dev/null | grep -a -c 'java/util/function/Supplier' || true)
  if [ "${hits:-0}" -gt 0 ]; then
    echo "${hits}	${jar}"
  fi
done | sort -rn

If the jar it prints matches the library you just updated, the hypothesis is nearly confirmed. I read the jars directly because a Gradle dependency tree tells you who depends on whom, not who uses which API.

The fix: turn on core library desugaring

For apps with minSdk below 24, Gradle can bundle Java 8+ standard APIs into the app at build time. In a Groovy build.gradle:

android {
    compileOptions {
        // Bundle Java 8+ standard APIs that old OS versions lack
        coreLibraryDesugaringEnabled true
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
 
dependencies {
    // Check the official docs for the current version before pinning
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
}

In the Kotlin DSL it is isCoreLibraryDesugaringEnabled = true. The setting rewrites references in dependency bytecode as well as your own, which is why one line in the app can cover a gap inside a library you cannot edit.

For me, adding it and rebuilding was enough to stop the Android 6.0.1 crashes. I rolled the release out in stages and watched the same exception fade from Crashlytics over several days.

Four clues I prepare before asking an agent

The handoff is where I lost the most time on this one, so I'll spend a little longer on it.

The first time, I pasted the whole stack trace into the agent and asked it to "find the cause." The agent started guessing from how newer devices behave, and the session drifted into reading a dependency's changelog line by line. It did not go well.

Now I prepare these four things first:

  1. OS version and API level. I write "Android 6.0.1 (API 23) only."
  2. minSdk and whether desugaring is on. I paste the relevant build.gradle lines as they are.
  3. The dependency I just updated, with before and after versions. The smaller the diff, the narrower the search.
  4. Where it reproduced and where it did not. "Not reproducible on an API 34 emulator" is a real clue.

Handing over an outline of what is missing, rather than only the symptom, shortened the investigation considerably. I may be missing a better way, but this is what worked for me.

Give the agent the outline of what is missing, not just the symptom. I try to keep to that even on days when I am tired.

Confirming the fix

  • Walk the same screen flow once on an old OS. An API 23 emulator is enough.
  • Check a release build. A debug build can pass while a release build, with R8 involved, behaves differently.

Both are plain chores, and skipping them is how I end up with the same morning again after the next update.

What to do next

Open your app's minSdk first. If it is below 24, check whether coreLibraryDesugaringEnabled is present. I look at those two places every time I update a library, and I'd suggest making it a habit too.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◉ Antigravity2026-10-17
Build the Environment Note Before You Write the Question — A Small Shell Script for Posts That Get Replies
A small shell script that gathers your OS, tool versions, and the tail of a log into one note you can paste into a forum post or issue, with secrets masked. Here is what it catches, what it misses, and the three lines only you can write.
◉ Antigravity2026-09-15
When an agent ends without a reason, I check the page's character encoding first
A page handed to ReadURL can end an Antigravity agent with nothing but Agent execution terminated. Here is how I tell those pages apart before handing them over, and why I now fetch and re-encode first.
◉ Antigravity2026-04-28
Antigravity Keeps Asking "Run command?" Even on Always Proceed: Causes and Fixes
Antigravity keeps prompting to approve commands you already allowed, or even with Always Proceed on? This separates what the official docs say about the terminal policy and allow/deny lists from the known bugs reported on the forum, with a practical order for narrowing it down.