ProGuard/R8: Obfuscation and Protection of Android Apps

Author: IT Sectr Published: 2026-02-14 Reading time: 8 min

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 — Java bytecode obfuscation and optimization tool, the standard for Android since the 2000s
  • R8 — Google's successor to ProGuard, built into AGP, performs obfuscation, minification, and optimization in a single pass
  • Obfuscation renames classes and methods to short names, complicating application reverse engineering
  • Minification removes unused classes, methods, and fields, reducing the final APK/AAB size
  • ProGuard rules (.pro files) control which parts of the code are preserved, obfuscated, or removed

What is ProGuard?

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 Architecture

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 PhasePurposeResult
ShrinkCall graph analysis and dead code removalFewer classes in APK
OptimizeMethod inlining, removal of unused parametersFaster code execution
ObfuscateRenaming classes, fields, and methodsReverse engineering protection
PreverifyAdding StackMap attributes for JVMJava 6+ compatibility

What is R8?

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.

kotlin
// 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.

Code Obfuscation in Android

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.

Why Obfuscation is Needed

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.

Example of ProGuard Rules

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.

pro
# =====================
# 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 and ShrinkResources

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.

ShrinkResources in Action

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.

xml
<!-- 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.

DirectivePurposeExample
-keepPreserves the class and all its members-keep class com.example.api.** { *; }
-keepclassmembersPreserves only class members-keepclassmembers class * { @SerializedName <fields>; }
-keepattributesPreserves bytecode metadata-keepattributes *Annotation*, Signature
-assumenosideeffectsRemoves calls without side effects-assumenosideeffects class Log { d(...); }
-dontwarnSuppresses warnings-dontwarn com.example.legacy.**

R8 vs ProGuard: Key Differences

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.

Comparison Table

CharacteristicProGuardR8
DeveloperGuardSquare (Eric Lafarge)Google
Release Year20022018 (stable in 2019)
Architecture4 separate phases (shrink → optimize → obfuscate → preverify)Single pass: shrink + optimize + obfuscate simultaneously
AGP IntegrationExternal tool, launched after javacBuilt into the D8 DEX compiler
Build Speed2–3 times slowerFaster due to single pass and native integration
Kotlin SupportLimited (issues with inline, lambdas, coroutines)Full: coroutines, inline functions, data class
Mapping Filemapping.txt (compatible with retrace)mapping.txt (same format)
Optimization Customization60+ options -optimizationpasses, -optimizationsLimited: most optimizations enabled by default
Support StatusReplaced by R8 (AGP 7.0+ does not use)Active development, part of AOSP

When R8 Can Break the Build

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.

pro
# 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.

Configuring ProGuard Rules

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.

Step 1: Basic Configuration

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.

Step 2: Libraries

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.

Step 3: Testing the Release Build

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.

Step 4: Mapping File and CI

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.

pro
# ===========================================
# 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

How is R8 different from ProGuard?

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.

Do I need to write ProGuard rules when using R8?

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.

How to enable R8 in an Android project?

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.

What is code obfuscation in Android?

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.

How to debug a crash log from an obfuscated application?

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

  • ProGuard — a classic Java bytecode obfuscation and optimization tool, consisting of four sequential phases
  • R8 — a modern successor from Google, built into AGP, performing all phases in a single pass with 2–3 times higher performance
  • Obfuscation renames classes, methods, and fields to short names, complicating reverse engineering and protecting the application's commercial logic
  • Minification removes unused code and resources, reducing APK size by 20–50% in typical projects
  • ProGuard rules (.pro files) control obfuscation behavior — directives -keep, -keepclassmembers, -assumenosideeffects specify which elements are preserved, removed, or renamed
  • Mapping file (mapping.txt) is a critically important build artifact for decoding crash logs from release builds via retrace
  • Testing the release build on a real device is mandatory — obfuscation issues only manifest at runtime and require adding missing -keep rules

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.

Discuss the project

Read also