Originally published on AppRanker.
If Play Console is showing you "DEX code optimization is below our threshold" next to something like "Obfuscation 2%", your release build almost certainly isn't running R8 at all. That is the default for several cross-platform templates, and from February 2027 it starts to matter. Below is what Google is measuring, which stacks ship with R8 off, and a release-safe way to switch it on.
What the warning is actually measuring
On August 26, 2026, Google Play announced new technical quality thresholds that take effect in February 2027. One of them is about DEX code: apps need at least 25% coverage on each of three metrics (shrinking, optimization and obfuscation) using R8 or another shrinker.
| Apps | Games | |
|---|---|---|
| Rule applies when DEX code exceeds | 10 MB | 50 MB |
| Minimum shrinking | 25% | 25% |
| Minimum optimization | 25% | 25% |
| Minimum obfuscation | 25% | 25% |
What each number means:
- Shrinking: the share of classes, fields and methods R8 is allowed to drop when unused.
- Optimization: the share it may rewrite, for example by inlining or merging classes.
- Obfuscation: the share it may rename to short identifiers.
Google says missing these "can affect an app's visibility and publishing capabilities on Google Play". A single-digit score doesn't mean R8 did a poor job. It means R8 never ran, or your keep rules left it almost nothing to touch.
Why a "JavaScript app" has 10 MB of DEX
Your Hermes bundle or AOT-compiled Dart isn't DEX. But the framework runtime, native modules, Firebase, ad SDKs, analytics and billing libraries are all Java or Kotlin bytecode, and that adds up fast. Whether R8 runs depends on the template you started from:
| Stack | R8 in release by default | Where to change it |
|---|---|---|
| React Native CLI | Off (enableProguardInReleaseBuilds = false) |
android/app/build.gradle |
| Expo SDK 57 and earlier | Off | expo-build-properties |
| Expo SDK 58 | On | android/gradle.properties |
| Flutter | Always on, optimize preset | android/app/proguard-rules.pro |
| Capacitor | Off, uses proguard-android.txt
|
android/app/build.gradle |
| .NET for Android / MAUI | Runs with -dontobfuscate hardcoded |
Tracked upstream (dotnet/android #12535) |
First, check whether the rule even applies to you. In Play Console go to Test and release > App bundle explorer, select your latest production bundle, and read the DEX size plus the three percentages. Under 10 MB? Not urgent yet, though still worth doing.
Turning R8 on, per stack
React Native CLI and Capacitor use the same release block in android/app/build.gradle:
def enableProguardInReleaseBuilds = true
android {
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
shrinkResources enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
}
Two gotchas: use proguard-android-optimize.txt, not proguard-android.txt (the old file contains -dontoptimize, and Android Gradle plugin 9 no longer supports it), and remove any leftover android.enableR8.fullMode=false from gradle.properties. On Capacitor, resist a blanket -keep class com.getcapacitor.** { *; }.
Expo SDK 57 and earlier: set it in app.json, then npx expo prebuild or rebuild on EAS. extraProguardRules is appended to proguard-rules.pro, so it survives a clean prebuild.
{
"expo": {
"plugins": [
["expo-build-properties", {
"android": {
"enableMinifyInReleaseBuilds": true,
"enableShrinkResourcesInReleaseBuilds": true,
"extraProguardRules": "-keep class com.example.reflected.Model { *; }"
}
}]
]
}
}
On SDK 58 R8 is already on. If a release build breaks after upgrading, fix the keep rules instead of opting out, because opting out puts you back under the threshold.
Flutter already runs R8, so a flagged Flutter app usually has a plugin or custom rule keeping whole packages. Dart's --obfuscate flag doesn't help here: it renames Dart symbols, not DEX.
Native Android / KMP: confirm isMinifyEnabled in the app module. AGP 9.3+ also adds an optimization block per build type, with keep rules allowed in src/<variant>/keepRules/*.keep.
Avoiding "works in debug, crashes in release"
R8 only touches release builds, so a debug build or dev client proves nothing. Run a real release variant on a physical device:
npx react-native run-android --mode release # React Native CLI
npx expo run:android --variant release # Expo, local
./gradlew bundleRelease # any Android project
Typical symptoms when R8 removes or renames something accessed by name at runtime: ClassNotFoundException at startup, NoSuchFieldException/NoSuchMethodException, or model fields that quietly vanish after JSON serialization. Expo issue #49779 is a good example: Calendar.createEventAsync() failed only in minified SDK 57 builds.
Rules for keep rules that fix crashes without sinking your scores:
- Read
app/build/outputs/mapping/release/missing_rules.txtfirst; R8 writes what it needs there. - Keep a class, not a package:
-keep class com.example.api.UserDto { *; }, nevercom.example.**. - Prefer
-keepclassmemberswhen a JSON parser only needs the fields. - Check whether the library already ships consumer rules before adding your own.
- Upgrade the crashing library; a missing rule is a library bug that may already be fixed.
Still flagged with R8 on? Run the analyzer
A broad keep rule is the usual culprit, and Google's R8 Configuration Analyzer ranks rules by how much code each blocks. On AGP 9.3+:
./gradlew :app:analyzeReleaseR8Config
# report: app/build/reports/r8/r8-config-analyzer-release.html
It also flags subsumed rules and rules matching nothing. Third-party SDKs are often to blame: one open issue on Google's ads mediation repo measured adapter consumer rules each blocking 5% to 10.6% of an app's live code.
Last thing: Play deobfuscates its own crash and ANR reports from the bundle (AGP 4.1+), but Sentry, Crashlytics and Bugsnag each need the mapping file uploaded through their own plugin or CLI. Verify that step in CI.
Key takeaways
- Each of shrinking, optimization and obfuscation must hit 25% by February 2027 if your DEX exceeds 10 MB (50 MB for games).
- React Native CLI, Capacitor and Expo SDK 57 and earlier ship with R8 off; Flutter and Expo SDK 58 have it on.
- Use
proguard-android-optimize.txtand test a real release build on a device. - Narrow, class-level keep rules beat package-wide ones; the R8 Configuration Analyzer shows which rules cost you most.
- Make sure your crash reporter gets the mapping file for every release.
The original has the full FAQ and sources in this guide to fixing the Play Console DEX code optimization warning with R8, and once your build is in shape you can list it on the AppRanker app leaderboard for Android and iOS apps.
What was the first thing that broke when you switched R8 on in your React Native, Expo or Flutter release build?
Top comments (0)