ProGuard and R8 are obfuscation, minification, and optimization tools for Android applications. ProGuard, created in 2002, was the de facto standard for Java code protection for a long time. R8 is its successor, developed by Google and built into the Android Gradle Plugin starting from AGP 3.4. Both tools reduce APK size, remove dead code, and complicate reverse engineering. According to Android Developers, R8 performs builds 2–3 times faster than ProGuard with comparable obfuscation quality.
Key Takeaways
ProGuard is an open-source tool (Apache 2.0) for obfuscation, minification, optimization, and preverification of Java bytecode. It was developed by Eric Lafarge in 2002 as part of the SourceForge project. ProGuard takes compiled Java classes (.class) or JAR archives as input and produces processed classes of the same format, but smaller in size and with renamed elements.
For a long time, ProGuard was the only standard for protecting Android applications from reverse engineering. Google officially recommended its use in the Android SDK and shipped a default configuration in the proguard-android-optimize.txt file inside the SDK tools. ProGuard worked as a separate tool, launched after Java code compilation into bytecode and before packaging into DEX.
ProGuard consists of four sequential phases: shrink (removal of unused classes), optimize (bytecode optimization — inlining, dead code removal), obfuscate (renaming classes, methods, and fields to short names), preverify (JVM compatibility checking). Each phase is controlled by separate rules from configuration files.
During the obfuscation stage, ProGuard generates a mapping file (mapping.txt) that maps original names to obfuscated ones. This file is critical for decoding crash logs from release builds using the retrace utility. Without a mapping file, a stack trace becomes a set of letters a(), b(), c() with no way to restore the original context.
| ProGuard Phase | Purpose | Result |
|---|---|---|
| Shrink | Call graph analysis and dead code removal | Fewer classes in APK |
| Optimize | Method inlining, removal of unused parameters | Faster code execution |
| Obfuscate | Renaming classes, fields, and methods | Reverse engineering protection |
| Preverify | Adding StackMap attributes for JVM | Java 6+ compatibility |
R8 is a next-generation obfuscation and minification tool from Google, first introduced in Android Studio 3.3 (November 2018) and becoming standard in AGP 3.4 (August 2019). Unlike ProGuard, R8 is part of the D8/R8 compiler that converts Java bytecode into DEX format. R8 performs all phases — obfuscation, minification, and optimization — in a single pass, without passing intermediate files between tools.
Google developed R8 with two goals: speed up builds (ProGuard worked as an external tool) and provide seamless integration with the modern Android stack (Desugar, Core Library Desugaring, D8). R8 is written in Kotlin and Java and is part of the R8/Desugar repository on AOSP (Android Open Source Project).
An important advantage of R8 is full backward compatibility with ProGuard rules. Existing .pro files work without changes. R8 even supports specific ProGuard directives, including -whyareyoukeeping, -printconfiguration, and -printmapping. This means the transition from ProGuard to R8 is transparent: simply update AGP.
// build.gradle.kts — enabling R8 via minifyEnabled
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// Basic configuration from Android SDK
getDefaultProguardFile("proguard-android-optimize.txt"),
// Custom project rules
"proguard-rules.pro"
)
}
}
}The code shows a standard release build configuration. The flag isMinifyEnabled = true activates R8 for obfuscation and optimization. isShrinkResources = true additionally removes unused resources. getDefaultProguardFile loads the default rules from the SDK, while proguard-rules.pro contains project-specific settings.
Obfuscation is the process of transforming source code into a form that is difficult for humans to analyze but retains full functionality. In the Android context, obfuscation means renaming classes, methods, and fields into short, meaningless names: com.example.app.auth.LoginManager becomes a.a.a, method authenticateUser becomes a, field userToken becomes b.
Android APK files are archives that can be opened with any archiver (ZIP, 7z, WinRAR). Without obfuscation, an attacker gets a complete map of the application: package names, classes, methods, and fields. Tools like jadx or Bytecode Viewer can restore near-original Java code from DEX files in seconds. Obfuscation does not make code invulnerable, but significantly raises the entry barrier: instead of meaningful names, the reader sees a(), b(), c().
Typical obfuscation goals: protecting commercial logic (algorithms, calculation formulas), hindering theft of API keys and tokens, preventing class substitution via reflection, and preventing APK patching and modification (repackage attack). In practice, 70% of tasks are solved by renaming alone — which is why ProGuard/R8 are used.
Below is a typical proguard-rules.pro file for an Android project with Retrofit, Gson, and Parcelable. The -keep rules preserve classes and methods necessary for library operation through reflection. Without these rules, R8 will remove or rename classes that the library accesses by string name.
# =====================
# Retrofit — preserving interfaces
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — JSON serialization
# =====================
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keep class *.serialization.** {
<fields>;
}
# =====================
# Parcelable — Creator
# =====================
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# =====================
# Logging — removing logs from release
# =====================
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(String, int);
public static int v(...);
public static int d(...);
public static int i(...);
public static int w(...);
public static int e(...);
}
# =====================
# Kotlin data classes — preserving constructors
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — entry point
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}Each directive in a .pro file solves a specific task. -keep prevents the entire class from being removed or renamed. -keepclassmembers only protects class members (fields and methods) but allows the class itself to be removed if unused. -assumenosideeffects tells R8 that a method call has no side effects and can be safely removed. The directive -keepattributes preserves metadata in bytecode — annotations, signatures, exceptions.
The rule -keep,allowobfuscation,allowshrinking for Retrofit allows R8 to rename interfaces but not remove them. This is necessary because Retrofit accesses interfaces through dynamic proxies (java.lang.reflect.Proxy), and removal would result in ClassNotFoundException at runtime. Similarly, Gson uses reflection to access fields annotated with @SerializedName — without -keepclassmembers the fields will be removed as unused.
Minification (shrinking) is the process of removing unused code and resources from the final build. ProGuard and R8 analyze the call graph starting from entry points (Activity, Service, BroadcastReceiver) and remove classes and methods that cannot be reached through the call chain. ShrinkResources is an additional stage that removes unused resources from res/ (layout, drawable, string, color).
Minification provides the greatest benefit in large projects with libraries. A typical scenario: a project uses 10% of the code from a connected library (e.g., Google Play Services). Without minification, all library code ends up in the APK. With minification, R8 removes 70–90% of library code, leaving only the classes and methods that are actually used. This directly impacts APK size, loading time, and memory consumption.
The ShrinkResources mechanism works in tandem with code minification. After R8 determines which classes are used, resource shrinking analyzes resource references from code: R.layout.main, R.drawable.icon, getString(R.string.title). All resources without a direct or indirect reference are removed from the final APK or AAB. This is done using the resource file resources.arsc and res/ folders.
An important nuance: resources can be accessed via getIdentifier() or Resources.getResourceName() by string name, bypassing the R class. In such cases, R8 does not see a direct link and may remove a resource that is actually used. To protect such resources, there is the directive -keep class **.R$* { *; } — it preserves all identifiers of the R class.
<!-- Example: a resource only used through getIdentifier() -->
<string name="dynamic_title_welcome">Welcome</string>
<string name="dynamic_title_share">Share</string>
<!-- Kotlin code accessing by string -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->In this case, R8 does not see a static reference to dynamic_title_welcome in the R class because the access is through getIdentifier with a dynamic name. To preserve such resources, add the directive -keepclassmembers class **.R$string { *; } to proguard-rules.pro — it prevents removal of any fields from all R$string classes.
| Directive | Purpose | Example |
|---|---|---|
| -keep | Preserves the class and all its members | -keep class com.example.api.** { *; } |
| -keepclassmembers | Preserves only class members | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | Preserves bytecode metadata | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | Removes calls without side effects | -assumenosideeffects class Log { d(...); } |
| -dontwarn | Suppresses warnings | -dontwarn com.example.legacy.** |
Despite R8 being the successor to ProGuard, there are fundamental differences between the tools in architecture, performance, and behavior. Google officially discontinued ProGuard support in Android Gradle Plugin starting from AGP 7.0, but ProGuard continues to be used in projects that require specific optimization behavior unavailable in R8.
| Characteristic | ProGuard | R8 |
|---|---|---|
| Developer | GuardSquare (Eric Lafarge) | |
| Release Year | 2002 | 2018 (stable in 2019) |
| Architecture | 4 separate phases (shrink → optimize → obfuscate → preverify) | Single pass: shrink + optimize + obfuscate simultaneously |
| AGP Integration | External tool, launched after javac | Built into the D8 DEX compiler |
| Build Speed | 2–3 times slower | Faster due to single pass and native integration |
| Kotlin Support | Limited (issues with inline, lambdas, coroutines) | Full: coroutines, inline functions, data class |
| Mapping File | mapping.txt (compatible with retrace) | mapping.txt (same format) |
| Optimization Customization | 60+ options -optimizationpasses, -optimizations | Limited: most optimizations enabled by default |
| Support Status | Replaced by R8 (AGP 7.0+ does not use) | Active development, part of AOSP |
R8 is more aggressive than ProGuard in removing code it considers dead. This leads to situations where the debug build works but the release build crashes with ClassNotFoundException or NoSuchMethodException. Typical cases: libraries using reflection by class name (Gson, Moshi, Retrofit, Room, Dagger); ServiceLoader or java.util.ServiceLoader calls; dynamic proxies (java.lang.reflect.Proxy); native methods (JNI). The solution is to add -keep for all classes that are called through reflection.
# Typical reflection issues — R8 does not see static linkage
# Room — preserving DAO and migrations
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — preserving components
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — not renaming native methods
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — preserving Binding classes
-keep class *.databinding.** { *; }If after adding rules the build still crashes, use the flag -printconfiguration full-config.txt in proguard-rules.pro. R8 will generate a full configuration file showing which rules are applied and which classes are preserved. Also useful is the directive -whyareyoukeeping class com.example.MyClass — it outputs the reason why R8 decided to preserve the given class.
Proper configuration of ProGuard rules is the key to stable obfuscation without runtime bugs. Below is a step-by-step setup process for a new project or for a project where obfuscation causes errors.
Start by including the standard Android SDK file — proguard-android-optimize.txt. It contains rules for basic Android components: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. This file is located in the SDK folder: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. If you use AGP, getDefaultProguardFile will load it automatically.
Each popular library has recommended ProGuard rules. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — all require specific -keep rules. Usually the rules are included in the AAR library and are linked automatically via consumer guard rules. Check that the library provides a proguard.txt file inside the AAR — this indicates the rules are already accounted for.
Before publishing, be sure to test the release build on a real device or emulator. Obfuscation issues only manifest at runtime. Check: authorization (login/registration), data loading from the network, navigation between screens, camera and gallery, push notifications, Deeplinks, WebView. Each crash in the release build needs to be decoded using retrace with the mapping file, and missing -keep rules need to be added.
The mapping file is generated at build/outputs/mapping/release/mapping.txt. This file must be preserved: without it, it is impossible to decode crash logs from Google Play Console. Include mapping.txt in your version control system or upload it to CI artifacts. Google Play Console accepts the mapping file automatically when uploading an AAB with uploading mapping.txt enabled.
Below is a complete obfuscation setup workflow in the proguard-rules.pro file with comments for each group of rules.
# ===========================================
# proguard-rules.pro — complete example
# ===========================================
# --- General settings ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- Android components ---
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.Fragment
-keep public class * extends androidx.fragment.app.Fragment
-keep public class * extends android.view.View
# --- OkHttp / Retrofit ---
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class retrofit2.** { *; }
-keepattributes Exceptions
# --- Gson / Moshi ---
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keep class com.google.gson.** { *; }
# --- Firebase ---
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }
# --- Kotlin Coroutines ---
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# --- Serialization ---
-keepclassmembers class * implements java.io.Serializable {
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# --- R8 only: forced preservation ---
# (ProGuard ignores this directive)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}After configuration, run the build: ./gradlew assembleRelease. Verify that files appeared in build/outputs/mapping/release/: mapping.txt (mapping of original to obfuscated names), seeds.txt (classes preserved by -keep rules), usage.txt (classes removed during minification). The APK size after obfuscation should decrease by 20–50% depending on the number of connected libraries.
Frequently Asked Questions
R8 is the successor to ProGuard, developed by Google. R8 performs obfuscation, minification, and optimization in a single pass, works 2–3 times faster than ProGuard, and is integrated directly into the Android Gradle Plugin. ProGuard uses four separate phases and requires external execution. Starting from AGP 7.0, ProGuard is not used — R8 works by default.
Yes, R8 uses the same ProGuard rules (.pro files). The directives -keep, -keepclassmembers, -keepattributes, -assumenosideeffects work identically. Basic rules come in proguard-android-optimize.txt from the Android SDK, while library-specific rules (Retrofit, Room, Gson) are added to the project's proguard-rules.pro. Without these rules, R8 may remove classes necessary for libraries that work through reflection.
R8 is enabled by default in Android Gradle Plugin starting from AGP 3.4. To activate minification, set isMinifyEnabled = true in the release buildType block of build.gradle.kts. The additional flag isShrinkResources = true enables removal of unused resources. In gradle.properties, you can force disable R8 via android.enableR8=false, but this is not recommended — R8 is faster and more stable.
Obfuscation — renaming classes, methods, and fields to short meaningless names (a, b, c). The class com.example.app.auth.LoginManager becomes a.a.a, method authenticateUser becomes a. This complicates application reverse engineering but does not affect execution logic. ProGuard and R8 only rename elements not protected by -keep rules. The mapping file preserves the correspondence of original and obfuscated names for decoding crash logs.
To decode a stack trace, use the retrace utility (part of ProGuard/R8 SDK). Command: retrace mapping.txt crash-stacktrace.txt. The mapping file is located at build/outputs/mapping/release/mapping.txt. Google Play Console also supports uploading mapping.txt when publishing an AAB — crash logs are automatically decoded in the console. Without a mapping file, the stack trace will only contain obfuscated names like a.b.c(), which is useless for debugging.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also