Skip to content

Android: breadcrumb sync throws ClassCastException (Double cannot be cast to String) since 8.14.0 #6306

@tomekzaw

Description

@tomekzaw

What React Native libraries do you use?

Hermes, RN New Architecture, Expo (mobile only), Expo Router

Are you using sentry.io or on-premise?

sentry.io (SaS)

Are you using any other error monitoring solution alongside Sentry?

No

@sentry/react-native SDK Version

8.13.0 → 8.14.0 (regression introduced in 8.14.0)

How does your development environment look like?

  • macOS, Android (issue is Android-only; iOS is unaffected)
  • React Native 0.85.3, Hermes + New Architecture enabled
  • Expo SDK 56, Expo Router

Sentry.init()

Sentry.init({
  dsn: "https://...@o...ingest.de.sentry.io/...",
  enabled: !__DEV__,
  environment: "production",
  enableTombstone: true,
  integrations: [Sentry.reactNavigationIntegration()],
});

Steps to Reproduce

  1. Use @sentry/react-native@8.14.0 on Android.
  2. Add any breadcrumb from JS — either manually (Sentry.addBreadcrumb({ message: "test" })) or via auto-instrumentation (navigation, console, etc.). The JS scope syncs it to the native scope via RNSentry.addBreadcrumb.
  3. Watch Logcat.

Expected Result

The breadcrumb is added to the native Android scope (with its message/category/data/timestamp), so it appears in native/tombstone crash reports. No error logged.

Actual Result

RNSentryBreadcrumb.fromMap logs an error and discards the breadcrumb, returning an empty fallback breadcrumb (only origin = "react-native"). The breadcrumb is lost from the native scope, so native crash reports lose breadcrumb context. Logcat (one entry per breadcrumb):

Failed to deserialize breadcrumb from map.
java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
	at io.sentry.util.MapObjectReader.nextStringOrNull(MapObjectReader.java:321)
	at io.sentry.util.MapObjectReader.nextDateOrNull(MapObjectReader.java:146)
	at io.sentry.Breadcrumb$Deserializer.deserialize(Breadcrumb.java:872)
	at io.sentry.react.RNSentryBreadcrumb.fromMap(RNSentryBreadcrumb.java:47)
	at io.sentry.react.RNSentryModuleImpl.lambda$addBreadcrumb$5(RNSentryModuleImpl.java:614)
	at io.sentry.react.RNSentryModuleImpl$$ExternalSyntheticLambda5.run(D8$$SyntheticClass:0)
	at io.sentry.Scopes.configureScope(Scopes.java:753)
	at io.sentry.Sentry.configureScope(Sentry.java:1121)
	at io.sentry.Sentry.configureScope(Sentry.java:1111)
	at io.sentry.react.RNSentryModuleImpl.addBreadcrumb(RNSentryModuleImpl.java:612)
	at io.sentry.react.RNSentryModule.addBreadcrumb(RNSentryModule.java:98)
	at com.facebook.jni.NativeRunnable.run(Native Method)
	at android.os.Handler.handleCallback(Handler.java:1095)
	at android.os.Handler.dispatchMessageImpl(Handler.java:135)
	at android.os.Handler.dispatchMessage(Handler.java:125)
	at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.kt:21)
	at android.os.Looper.loopOnce(Looper.java:296)
	at android.os.Looper.loop(Looper.java:397)
	at com.facebook.react.bridge.queue.MessageQueueThreadImpl$Companion.startNewBackgroundThread$lambda$0(MessageQueueThreadImpl.kt:152)
	at java.lang.Thread.run(Thread.java:1572)

Root cause

The JS SDK (@sentry/core) stamps breadcrumb.timestamp as a number (epoch seconds) and forwards it unchanged to native (scopeSyncwrapper.addBreadcrumbRNSentry.addBreadcrumb). Over the bridge it becomes a java.lang.Double.

On Android, RNSentryBreadcrumb.fromMap now feeds that map to the wire-format Breadcrumb.Deserializer, which reads timestamp via MapObjectReader.nextDateOrNullnextStringOrNull and requires an ISO-8601 string, so a Double throws ClassCastException.

This was introduced by #6261 ("Use native SDK deserializers for User and Breadcrumb"), first shipped in 8.14.0. Before that, fromMap parsed fields manually and ignored the numeric timestamp, so it didn't crash. iOS is unaffected because it uses the Cocoa SDK's lenient initWithDictionary:.

Note: User was migrated to the same deserializer in #6261, so its date/numeric fields may have the same issue.

Workaround

Normalize the numeric timestamp to an ISO-8601 string in fromMap before deserializing:

final Map<String, Object> map = toDeepHashMap(from);
final Object timestamp = map.get("timestamp");
if (timestamp instanceof Number) {
  map.put("timestamp",
      io.sentry.DateUtils.getTimestamp(new java.util.Date((long) (((Number) timestamp).doubleValue() * 1000))));
}
final MapObjectReader reader = new MapObjectReader(map);

Metadata

Metadata

Assignees

No fields configured for issues without a type.

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions