How to Configure ProGuard and R8 in Android Apps
In this tutorial, you'll learn about How to Configure ProGuard and R8 in Android Apps. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Problem
Your Android release build crashes with ClassNotFoundException or NullPointerException because ProGuard or R8 stripped away classes, methods, or fields that are accessed via Reflection. By default, the shrinker removes all code it considers unused. Libraries like Gson, Retrofit, Room, and Glide rely on Reflection and will break without explicit keep rules.
Quick Fix
1. Enable R8 with full mode
In gradle.properties (project-level):
Android.enableR8.fullMode=true
In your module-level build.gradle:
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
2. Add keep rules for Reflection-based libraries
In proguard-rules.pro:
# Keep all model classes used by Gson
-keep class com.yourapp.model.** { *; }
# Keep serialization fields
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
}
# Keep fields annotated with Gson's @SerializedName
-keepclassmembers class com.yourapp.** {
@com.google.gson.annotations.SerializedName <fields>;
}
# Keep Retrofit interfaces
-keep,allowobfuscation interface com.yourapp.api.**
# Keep Room entities
-keep class * extends androidx.room.RoomDatabase
-keep @androidx.room.Entity class *
# Keep annotations for libraries that read them at runtime
-keepattributes Signature, *Annotation*, EnclosingMethod, InnerClasses
3. Build the release APK and inspect the mapping
./gradlew assembleRelease
Expected output:
> Task :app:minifyReleaseWithR8
> Task :app:shrinkReleaseRes
BUILD SUCCESSFUL in 1m 12s
Read the mapping file to see what was kept or renamed:
cat app/build/outputs/mapping/release/mapping.txt | grep "com.yourapp" | head -10
Expected output:
com.yourapp.MainActivity -> com.yourapp.a:
com.yourapp.model.User -> com.yourapp.b:
4. Decode a stack trace from a crash report
Use the retrace tool bundled with the Android SDK:
retrace.sh -verbose app/build/outputs/mapping/release/mapping.txt stacktrace.txt
Expected output (deobfuscated):
com.yourapp.MainActivity.onCreate(MainActivity.java:42)
com.yourapp.network.ApiClient.fetchData(ApiClient.java:88)
5. Debug why a class was removed
Use -whyareyoukeeping to understand the shrinker's decisions:
-whyareyoukeeping class com.yourapp.model.User
Build and check the output:
./gradlew assembleRelease 2>&1 | grep "whyareyoukeeping"
Expected output:
[PROGUARD] com.yourapp.model.User is kept by: line 42: -keep class com.yourapp.model.** { *; }
6. Use the R8 inspector for deeper analysis
./gradlew assembleRelease -P R8_inspector=true
This generates an inspector.html file in build/outputs/ with an interactive UI showing which classes, methods, and fields were kept, renamed, or removed.
7. Use consumer ProGuard rules in libraries
If you maintain an Android library, ship default keep rules with it:
// In the library's build.gradle
android {
defaultConfig {
consumerProguardFiles 'consumer-rules.pro'
}
}
The library's rules are automatically applied to any app that consumes it.
Prevention
- Test your release build with
minifyEnabled truebefore every production release - Archive the
mapping.txtfile for each published version to deobfuscate future crash reports - Use
-keeprules only when necessary — too many keep rules defeat the purpose of shrinking - Add keep rules for every library that uses Reflection (Gson, Retrofit, Room, Glide)
- Run R8's full mode in staging first, then enable it in production only after validating the behavior
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro