Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If an Android app works in debug but loses Gson values, fails to instantiate a model, or cannot find a class in its minified release build, R8 may have removed or renamed something that is accessed dynamically. The fix is not automatically a broad keep rule: identify what reflection needs, give serialized properties stable names, preserve only required constructors and metadata, and test the optimized artifact.

Why reflection behaves differently after minification

R8 can shrink, optimize, and obfuscate code. Ordinary static analysis can miss a dependency that exists only at runtime—for example, a class name assembled as a string, a constructor found reflectively, an annotation scan, or Gson inspecting fields. If R8 sees no direct reference, it may remove code; if it renames a class or member that another component looks up by its original name, that lookup may fail.

This is why a debug build can work while the release build fails. Android’s keep-rules guidance describes conditional rules for reflection-based patterns, including code that reflectively calls a method when a matching class or member is present. The right rule depends on the actual runtime dependency, not simply on the fact that reflection is involved.

What can fail in Gson serialization

“R8 Gson fields null” and “Gson serialization broken after obfuscation” describe symptoms, not one diagnosis. A missing JSON property, a null field after deserialization, a constructor that no longer runs, a reflective instantiation failure, incorrect generic-type handling, or a duplicate JSON field name in a class hierarchy can have different causes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Fields missing or null: check whether the field is present in the JSON, whether the intended property name is used, and whether the relevant field and annotations survive minification.
  • Instantiation or default-value changes: check which constructor Gson uses and whether the model’s constructor shape depends on an implicit parameter, as can happen with a non-static inner class.
  • Generic values decoded incorrectly: check use of TypeToken and whether the generic signature information required at runtime is retained.
  • Duplicate field-name exception: check fields across the inheritance hierarchy. Renamed fields can collide in Gson’s view of a class hierarchy unless serialized names are made distinct.

Gson’s Android R8 / ProGuard troubleshooting guidance warns that open-ended reflection makes Gson a poor fit for Android minification, including when Gson’s bundled rules are present. It recommends testing the minified build rather than assuming the library rules cover every application model.

Keep source names only when runtime lookup needs them

There are two separate naming contracts to consider. A reflective lookup that searches for a class or member by its original code name requires that name to remain stable. A JSON consumer, by contrast, needs a stable wire-property name; it does not inherently need the Java field’s source name to remain unchanged.

For Gson JSON properties, use @SerializedName when a property name is part of an external or persisted data format:

@SerializedName("account_id")
String accountId;

The annotation value specifies the JSON name, so the Java field can be renamed without changing that wire name. R8’s FAQ (version 8.2.22) explains that fields annotated with Gson @SerializedName may still be obfuscated because the annotation value controls the JSON property name. Use distinct explicit names for fields that would otherwise collide across a superclass and subclass.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Do not add name-preservation rules just to keep JSON keys stable when explicit serialized names solve that contract. Conversely, @SerializedName does not preserve every class, constructor, annotation, or generic signature needed by a reflective library.

What must be kept in R8 full mode

R8 full mode is more aggressive than compatibility mode. In its versioned 8.2.22 FAQ, R8 says reflected-only classes need explicit keeping, default constructors are not implicitly kept, and attributes such as annotations and Signature survive only for program elements matched by keep rules. A rule that retains a field alone may therefore be insufficient if the runtime also needs its containing class, a constructor, an annotation, or generic signature metadata.

Gson’s upstream bundled gson.pro rules preserve items such as Signature, visible annotations and defaults, TypeToken and subclasses, and certain Gson-annotated members. The file explicitly notes that application-specific rules may still be required, including for particular fields or no-argument constructors. Treat those bundled rules as a baseline, not a guarantee that every reflective model is covered.

For “ProGuard reflection keep rules,” first establish what is looked up dynamically, then match the rule to that use. Android supports conditional keep rules that retain a reflective member only when the corresponding class pattern exists. This can avoid preserving unrelated code. Do not copy a broad rule from an old example without checking the R8 version, full-mode behavior, library consumer rules, and the runtime feature that needs protection.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the narrowest workable Gson strategy

Approach Name stability Reflection surface Trade-off
Broad keep rules Can preserve original class or member names if the rule specifies that. Retains a larger model or code surface. Simple to try, but can limit shrinking and obfuscation and may still omit required constructors or attributes if scoped incorrectly.
Constrained reflective models Use @SerializedName for stable JSON names; preserve original code names only for actual name-based lookups. Keep model shapes predictable: use a no-argument constructor where appropriate and make models top-level or static. Less open-ended than arbitrary reflection, but still requires release-build testing and rules for the required elements.
Explicit adapters or JSON APIs Define the JSON mapping explicitly rather than relying on field-name discovery. Use a TypeAdapter, TypeAdapterFactory, Gson’s JSON tree APIs, or manual readers and writers. More implementation work, but reduces dependence on reflective discovery of model fields and constructors.

Gson’s current guidance recommends constrained models or explicit adapters and APIs as alternatives to open-ended reflection. The best option depends on how many types are involved and how much control the app needs over parsing. If the serialized format is important to clients or stored data, explicit mappings can make that contract easier to reason about than relying on source identifiers.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Exclude fields that are not part of the JSON contract

Not every field should be preserved for serialization. If a field is deliberately not part of the JSON representation, mark it transient when that matches the intended behavior; Gson/R8 guidance identifies this as a way to omit a field. This is an exclusion decision, not a fix for a broken release build.

Before excluding a field, decide whether it is truly outside the data contract. Removing a field from serialization can change persisted output or break communication with another system. If the field must be serialized, give it an explicit, distinct @SerializedName rather than hiding a collision or minification issue by excluding it.

How to diagnose and verify a minified release

  1. Find the dynamic access. Identify whether the failure involves a string-based class or member lookup, constructor lookup, annotation discovery, Gson field reflection, TypeToken, or a library’s reflective bridge.
  2. Identify the contract. Determine which code names must remain unchanged for runtime lookup and which JSON names must remain stable for stored or external data. Use explicit serialized names for the latter.
  3. Preserve only what that path needs. Check whether the rule must cover the class, member, constructor, annotation, or generic signature. Review the rules shipped by dependencies as well as application rules.
  4. Exercise the minified release variant. Test serialization and deserialization with representative payloads, including older and newer data shapes where relevant. Verify field values, constructor behavior, generic types, and polymorphic cases that the app uses.
  5. Inspect mappings when names are involved. R8 mapping information can help identify obfuscated names and retrace stack traces. Use it to distinguish a name mismatch from removal, missing metadata, or a model/constructor issue.

A passing debug test does not verify the optimized artifact. The release variant with its actual minification configuration is the relevant test target.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does the same advice apply beyond Gson?

The underlying principle applies to other reflection-driven systems: dynamic access can be invisible to static reachability analysis, and code or metadata may need explicit preservation. But the annotations, metadata requirements, and keep rules vary by library. Check that library’s own documentation rather than applying Gson-specific annotations or assumptions to another serializer, dependency-injection framework, or reflective bridge.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.