Skip to content

Fix NoClassDefFoundError for DefaultJsonSerializer$LazyHolder in ProGuard/R8 release builds#530

Draft
Copilot wants to merge 1 commit into
masterfrom
copilot/investigate-android-sdk-issue
Draft

Fix NoClassDefFoundError for DefaultJsonSerializer$LazyHolder in ProGuard/R8 release builds#530
Copilot wants to merge 1 commit into
masterfrom
copilot/investigate-android-sdk-issue

Conversation

Copy link
Copy Markdown

Copilot AI commented May 14, 2026

Summary

Fixes NoClassDefFoundError: com.optimizely.ab.event.internal.serializer.DefaultJsonSerializer$LazyHolder that prevents experiment impression events and conversion/purchase events from being dispatched in minified release builds (Android SDK 5.1.1+).

Root Cause

LogEvent.getBody() (called from EventWorker.getData()) invokes DefaultJsonSerializer.getInstance(), which accesses DefaultJsonSerializer$LazyHolder.INSTANCE using the initialization-on-demand holder (lazy initialization) pattern:

private static class LazyHolder {
    private static final Serializer INSTANCE = create();
}

proguard-rules.txt already keeps com.optimizely.ab.event.internal.payload.** (the event payload model classes), but had no keep rule for com.optimizely.ab.event.internal.serializer.**. R8/ProGuard therefore strips DefaultJsonSerializer and its $LazyHolder inner class during the release build, causing a NoClassDefFoundError at runtime the first time an event is dispatched.

Because the exception occurs before the HTTP request is made, no events reach the /v1/events endpoint — explaining why impression and conversion events silently fail while a custom EventHandler that manually serializes with Gson works fine.

Changes

Added three ProGuard/R8 keep rules to proguard-rules.txt (which is already wired as consumerProguardFiles in the android-sdk module):

Rule Why
-keep class ...DefaultJsonSerializer { *; } Entry point called by LogEvent.getBody()
-keep class ...DefaultJsonSerializer$LazyHolder { *; } Inner class stripped by R8 — directly causes the NoClassDefFoundError
-keep class ...GsonSerializer { *; } The implementation DefaultJsonSerializer.create() selects at runtime on Android (Gson is bundled; Jackson is not)

Testing

This is a ProGuard-only configuration change. The fix can be verified by building a minified release APK (minifyEnabled true) and confirming:

  1. No NoClassDefFoundError in logcat when decide() or track() is called.
  2. Impression and conversion events appear in the Optimizely dashboard without a custom EventHandler.

…ng ProGuard/R8 keep rules

Agent-Logs-Url: https://github.com/optimizely/android-sdk/sessions/b3341336-5cfc-4bda-a258-73558bcf3d85

Co-authored-by: muzahidul-opti <129880873+muzahidul-opti@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants