Mr.AndroidShin / Dev Tools

Debugging log · Release build

It works in debug and crashes in release — the R8 keep-rule trap

The app ran flawlessly all through development. Then I built the release version, installed it, and it crashed on the first screen that loaded data. Same code, same device — only the build type changed. This is one of the most common "but it worked yesterday" traps in Android, and it almost always comes down to one thing: R8.

The short version: release builds run R8 (code shrinking + obfuscation). It removes code it thinks is unused and renames what's left. Anything you reach by reflection — Gson/Moshi models, serialization, class names loaded by string — gets stripped or renamed, and crashes. The fix is to add keep rules for exactly those pieces.

Why only the release build?

Debug builds don't shrink or obfuscate, so every class and method is present with its original name. Release builds enable R8 via minifyEnabled true, which does two things: it removes code that appears unused, and it renames classes, methods and fields to short names to shrink the app. R8 decides "unused" by tracing calls it can see in the code. What it can't see is reflection — when you look something up by name at runtime — so it happily deletes or renames the very classes your reflection depends on.

Reading the crash

The stack trace usually points straight at the culprit, even though the names look scrambled:

java.lang.ClassNotFoundException: Didn't find class "a.b.c"
// or
com.google.gson.JsonSyntaxException / NullPointerException
// on a model whose fields all came back null after minify

Two tell-tale signs: a ClassNotFoundException/NoSuchMethodException for something you know exists, or a data model that parses to an object with all fields null. The second one is sneaky — it doesn't crash immediately; R8 renamed the fields, so Gson can't match the JSON keys, and everything comes back empty.

The fix: keep rules

Tell R8 to leave the reflected pieces alone in your proguard-rules.pro. A few of the ones that saved me:

Keep your data models

# Models parsed by Gson/Moshi — keep names and fields intact
-keep class com.yourapp.model.** { *; }
-keepclassmembers class com.yourapp.model.** { *; }

Keep annotations and generic signatures

-keepattributes Signature, *Annotation*, InnerClasses, EnclosingMethod

Serialization libraries read annotations and generic types at runtime; strip these and parsing silently misbehaves.

Follow the library's own rules

Most libraries that use reflection ship their own keep rules — you just have to not fight them. Check the docs for Gson, Moshi, Retrofit, Room and your DI framework; add the consumer rules they recommend rather than inventing your own.

Don't reach for minifyEnabled false. Turning R8 off "fixes" the crash but ships a bigger, un-optimized app and hides the real problem. Keep shrinking on and add targeted keep rules.

How to debug it properly

Package names above are placeholders — swap in your own model and serialization packages.