Fixing "Class Not Found" Errors in Android: From ADT to Modern Android Studio
The error message Could not find class 'com.xxxnx.adt.Find$PlaceUnitListener', referenced from method com.xxxnx.adt.Find.<init> is one of those Android development gotchas that can eat an afternoon if you don’t know what’s happening.
I ran into this in 2019 on a project that was stuck on Eclipse ADT (yes, that was still a thing), and then encountered a variation of it again in 2022 with Android Studio after ProGuard did its thing. Different decade, same error, different root cause.
Here’s how to fix it in both scenarios.
The ADT Build Path Export Problem
When you’re using Eclipse ADT and see this error at runtime on the device, even though the class clearly exists in your source code — the problem is usually the Java Build Path export order.
What Happens
Eclipse compiles your project and generates .class files. But when it packages everything into the APK, it only includes classes that are in “exported” JAR entries on the build path. If an inner class is defined in a library that’s part of your project but not properly exported, the APK compiler doesn’t know to include it.
The runtime then fails when it tries to load the outer class and can’t find the referenced inner class.
The Fix in ADT
Navigate to: Project → Properties → Java Build Path → Order and Export
You’ll see a list of your libraries and project sources. They have checkboxes on the left (for export) and order matters on the right.
- Select your library JARs
- Check the “export” checkbox next to each one
- Move them above any project sources that depend on them
- Click OK
Then clean and rebuild:
Project → Clean → Clean all projects → Build All
The key is the combination of: correct export checkbox state AND correct ordering. Both matter.
Why This Happens
It’s a classpath vs. runtime classloader mismatch. Eclipse’s compiler sees all the classes because it has the full build path. But the APK builder is more selective — it only pulls in what the export entries tell it to. Inner classes in non-exported JARs get left behind.
The Modern Android Studio Version (2024-2026)
If you’re on Android Studio in 2024 or 2025 and you see something like this, the most common cause is R8 stripping classes that it thinks are unused.
ProGuard is deprecated. Since AGP 8.0 (2023), R8 is the only shrinker. All ProGuard configuration syntax is compatible with R8, but ProGuard itself is no longer maintained. If you’re still using proguard-android.txt, migrate to R8’s proguard-rules.pro.
The R8 Problem
R8 does dead code elimination. It analyzes your code, figures out which classes are referenced, and removes the rest. The problem is that reflection and dynamic class loading fool this analysis.
Here’s a class that triggers this:
// This pattern will cause R8 to strip inner classes
package com.example.myapp;
public class EventManager {
// R8 can't see this inner class being used
class AnalyticsListener implements EventSubscriber {
@Override
public void onEvent(Event event) {
// handle event
}
}
public EventManager() {
// Dynamically registering the inner class
EventBus.register(AnalyticsListener.class, new AnalyticsListener());
}
}
R8 sees AnalyticsListener as a class that’s never directly referenced by name in the code — it’s only used via the inner class reference inside the constructor. So it deletes it.
The Fix: Keep Rules
Add R8 rules to preserve the inner classes:
# proguard-rules.pro
# Keep inner classes by keeping their outer class
-keep class com.example.myapp.EventManager$AnalyticsListener { *; }
# Or keep all inner classes of a specific outer class
-keep class com.example.myapp.EventManager$* { *; }
# Or preserve by specific naming pattern
-keepclassmembers class com.example.myapp.**$* {
<init>();
}
# If using EventBus or similar libraries, add their keep rules
-keep class de.greenrobot.event.** { *; }
-keepclassmembers class de.greenrobot.event.** { *; }
AGP 8.0+ Configuration
For AGP 8.x, ensure your namespace is declared and multidex is enabled if needed:
// build.gradle (app level)
android {
namespace 'com.example.myapp' // Required in AGP 8.0+
compileSdk 34
defaultConfig {
multiDexEnabled true // Required for > 64K methods
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
debug {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions { jvmTarget = '17' }
}
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}
R8 Full Mode (AGP 8.0+)
R8 full mode provides more aggressive optimization and better tree-shaking. Enable it in gradle.properties:
# gradle.properties
android.enableR8.fullMode=true
Full mode can break more code (it removes classes that regular R8 would keep), so you may need additional keep rules. But it produces smaller APKs and surfaces hidden dependency issues.
Debugging R8 with -whyareyoukeeping
When you can’t figure out why R8 is keeping or removing a class:
# Add to proguard-rules.pro
-whyareyoukeeping class com.example.myapp.SomeClass
R8 outputs the reason at the end of the shrink pass, showing which class or rule is referencing the target.
Check seeds.txt for kept classes
After a release build, check what R8 actually kept:
# Find the seeds file in the build output
cat app/build/outputs/mapping/release/seeds.txt
# Also useful: check what was removed
cat app/build/outputs/mapping/release/usage.txt
Multidex: The Third Root Cause
If you’re on an older minSdkVersion (below 21) and using multidex, there’s another failure mode: the primary DEX file can’t include all your classes, and the inner class ends up in a secondary DEX that isn’t loaded.
The Problem
Before Android 5.0 (API 21), Android only loads classes from the primary DEX file at startup. If your inner class is in classes2.dex instead of classes.dex, it won’t be found.
AGP 8.x improved multidex inlined shrinking, reducing the number of DEX files and improving cold start. But if you’re still targeting pre-21 devices, this applies.
The Fix: MultiDexApplication
// In your Application class
public class MyApp extends MultiDexApplication {
// MultiDex installs the additional DEX files at startup
}
// If you already have an Application class
public class MyApp extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
Verify what’s in each DEX file
# Check which DEX contains a class
unzip -l app/build/outputs/apk/release/app-release.apk | grep "MyClass"
# List all DEX files in the APK
unzip -l app/build/outputs/apk/release/app-release.apk | grep "classes"
Kotlin 2.0 + K2 Compiler Interactions (2024+)
Kotlin 2.0 with the K2 compiler (released 2024) changed how Kotlin metadata is compiled. R8 keep rules may need updating, especially for:
- Data classes used with Gson/Jackson serialization
- Sealed classes with complex inheritance
- Inline classes (value classes)
# Kotlin-specific rules for K2 compiler
-keepattributes *Annotation*
-keepattributes RuntimeVisibleAnnotations
-keepattributes RuntimeInvisibleAnnotations
# Kotlin metadata
-keep class kotlin.Metadata { *; }
# Keep data classes used with serialization
-keep class com.example.myapp.model.** { *; }
-keepclassmembers class com.example.myapp.model.** {
<fields>;
}
Debugging the Runtime ClassNotFoundError
When the error happens on the device, here’s how to get more information:
# Enable verbose dalvikvm logging via adb
adb shell setprop log.tag.dalvikvm VERBOSE
adb logcat -v threadtime | grep -i "not found"
# Dump the APK's DEX contents
adb shell dumps dex /data/app/com.example.myapp-1/base.apk
# Or use apkanalyzer (from Android SDK)
$ANDROID_HOME/build-tools/34.0.0/apkanalyzer dex list \
app/build/outputs/apk/release/app-release.apk | grep "MyClass"
This will show you the actual class that couldn’t be loaded and the classloader path. The classloader path tells you which APK or DEX file it’s looking in.
If the class is in the APK, it’ll show up in the DEX dump. If it’s not, you know R8 stripped it or the build process failed to include it.
What Changed Recently (2024-2026)
- R8 replaced ProGuard entirely. ProGuard hasn’t been updated since AGP 8.0. All configuration syntax is R8-compatible, but use R8’s fullMode for best results.
- AGP 8.x requires namespace declarations. Missing
namespacein build.gradle causes runtime ClassNotFound on some devices. - Kotlin 2.0 K2 compiler changed metadata compilation, requiring updated keep rules for data classes and sealed classes.
- App Bundles increased ClassNotFound risk. Split APKs mean the runtime DEX class loader resolves classes across split APK boundaries — missing classes that were previously bundled together now fail.
- AndroidX migration completed. By 2024, mixing Support Library and AndroidX caused ClassNotFound on some devices. Complete the migration before targeting API 34+.
Common Gotchas (2024-2026)
R8 strips classes used only by reflection. Class loaded by name (Class.forName("com.example.MyPlugin")) will be stripped. Always add explicit keep rules for plugin systems.
Kotlin data classes + Gson. Kotlin data classes may not serialize correctly with Gson without -keepattributes Signature and model keep rules.
JNI classes must be fully kept. If a class has native methods (JNIEXPORT), the entire class must be kept. R8 will remove native methods if it thinks the class is unused.
AGP 8.0 namespace requirement. Every module must declare namespace in its build.gradle. Without it, R8 may produce a different package structure than expected, causing ClassNotFound.
Multidex primary DEX must contain startup classes. R8’s keep rules in the main DEX configuration determine what’s in classes.dex. Startup classes (Application, first Activity) must be there.
The Checklist
When you hit this error:
- ADT projects: Check Java Build Path → Order and Export. Clean and rebuild.
- Android Studio + R8: Add
-keeprules for inner classes. Checkseeds.txtto see what R8 kept. - Multidex on old devices: Make sure your Application extends
MultiDexApplication. - Verify the class is in the APK: Use
unzip -lorapkanalyzer dex list. - Check for obfuscation: If class names look weird (a, b, c), R8 renamed them and you need more keep rules.
- Check namespace declarations in AGP 8.0+ — missing namespaces cause class resolution failures.
- Use -whyareyoukeeping when you can’t determine why a class was removed.
The error is almost always a build configuration issue — the class exists in source, it’s just not making it into the runtime APK. Work backward from the APK to figure out where the filtering is happening.
For more on Android build issues, the post on Gradle troubleshooting covers related configuration problems. The SDET guide has a broader section on CI/CD pipeline optimization that applies to Android CI workflows as well.
Comments