DEV Community

Cover image for DEX code optimization warning: enable R8 without crashes
ExtensionBooster
ExtensionBooster

Posted on Originally published at appranker.net

DEX code optimization warning: enable R8 without crashes

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"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 { *; }"
        }
      }]
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Read app/build/outputs/mapping/release/missing_rules.txt first; R8 writes what it needs there.
  2. Keep a class, not a package: -keep class com.example.api.UserDto { *; }, never com.example.**.
  3. Prefer -keepclassmembers when a JSON parser only needs the fields.
  4. Check whether the library already ships consumer rules before adding your own.
  5. 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
Enter fullscreen mode Exit fullscreen mode

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.txt and 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)