perf(android): Replace Date with unix timestamp in SentryNanotimeDate (JAVA-533)#5550
Merged
runningcode merged 8 commits intoJun 17, 2026
Merged
Conversation
📲 Install BuildsAndroid
|
Contributor
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a416a65 | 333.78 ms | 410.37 ms | 76.59 ms |
| d15471f | 304.55 ms | 408.43 ms | 103.87 ms |
| ca6b6d8 | 380.45 ms | 460.38 ms | 79.93 ms |
| c8125f3 | 397.65 ms | 485.14 ms | 87.49 ms |
| b936425 | 302.69 ms | 372.86 ms | 70.17 ms |
| ed33deb | 334.19 ms | 362.30 ms | 28.11 ms |
| cf708bd | 408.35 ms | 458.98 ms | 50.63 ms |
| eb95ded | 317.51 ms | 369.08 ms | 51.57 ms |
| a5ab36f | 320.47 ms | 389.77 ms | 69.30 ms |
| 80fd6ad | 321.06 ms | 375.79 ms | 54.73 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a416a65 | 1.58 MiB | 2.12 MiB | 555.26 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| ca6b6d8 | 0 B | 0 B | 0 B |
| c8125f3 | 1.58 MiB | 2.10 MiB | 532.32 KiB |
| b936425 | 0 B | 0 B | 0 B |
| ed33deb | 1.58 MiB | 2.13 MiB | 559.52 KiB |
| cf708bd | 1.58 MiB | 2.11 MiB | 539.71 KiB |
| eb95ded | 0 B | 0 B | 0 B |
| a5ab36f | 1.58 MiB | 2.12 MiB | 555.26 KiB |
| 80fd6ad | 0 B | 0 B | 0 B |
7 tasks
markushi
approved these changes
Jun 17, 2026
… (JAVA-533) SentryNanotimeDate stored a java.util.Date but only ever read its epoch millis. Storing the millis directly avoids a Calendar allocation on every timestamp, which on Android backs every span/transaction timestamp. The default constructor now uses System.currentTimeMillis() instead of DateUtils.getCurrentDateTime() (Calendar with UTC). This is behavior- preserving: the UTC TimeZone only affects calendar field access, not the epoch-millis value the class used. BREAKING: the public SentryNanotimeDate(Date, long) constructor is replaced by SentryNanotimeDate(long unixDate, long nanos). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SentryNanotimeDate is the legacy Date+nanoTime precision workaround and is not intended for direct use by consumers. Marking it @ApiStatus.Internal signals this and means the constructor change in this PR is not a public API break per the repo's API policy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Name the long field for its unit so it is clear it holds the unix timestamp in milliseconds since the epoch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b80927b to
48adb69
Compare
0xadam-brown
left a comment
Member
There was a problem hiding this comment.
So good to see us getting rid of Date. Nice! 💯
A few quick comments, one of which could block us. Let me know what you think.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(JAVA-533) The previous change replaced the (Date, long) constructor with a (long, long) constructor, which was a breaking API change. Add the Date constructor back, delegating to the millis-based one, and mark it deprecated to steer callers toward the new constructor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…VA-533) Error Prone flagged the deprecated (Date, long) constructor as inlineable, failing the build. Suppress the suggestion to match the existing convention in Sentry.java, keeping the constructor available for backwards compatibility. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Error Prone's JavaUtilDate check flagged date.getTime() in the deprecated constructor, failing the build. Suppress it, matching the existing suppression used elsewhere in this class. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
runningcode
commented
Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📜 Description
SentryNanotimeDatestored ajava.util.Datebut only ever read its epoch millis (incompareToand viaDateUtils.dateToNanos). This replaces the storedDatewith along unixDate(milliseconds since the epoch), and the default constructor now callsSystem.currentTimeMillis()instead ofDateUtils.getCurrentDateTime()(which allocates aCalendar).This is behavior-preserving:
Date.getTime()already returns epoch millis, and the UTCCalendarused bygetCurrentDateTime()only affected calendar field access — never the absolute instant the class actually used. SoCalendar.getInstance(UTC).getTime().getTime() == System.currentTimeMillis().SentryNanotimeDateis now marked@ApiStatus.Internal— it is the legacyDate+nanoTimeprecision workaround and is not intended for direct use by consumers (useSentryLongDate/SentryInstantDateto build aSentryDate). Its constructor changed from(Date, long)to(long unixDate, long nanos), and the now-unused internalDateUtils.dateToNanos(Date)is removed. Per the repo's API policy,@ApiStatus.Internaltypes are not part of the public contract.💡 Motivation and Context
JAVA-533, part of the "Reduce SDK init time / allocations" effort. On Android,
SentryNanotimeDateis always the active date provider (SentryAndroidDateProvidernever switches toInstant), sonow()runs this on every span/transaction timestamp. Removing the per-timestampCalendar+Dateallocation reduces CPU and GC pressure that scales with tracing volume.Performance
JVM microbenchmark (JDK 17, isolating the differing part of the constructor; both paths also call
System.nanoTime(), excluded):Calendar.getInstance(UTC).getTime()System.currentTimeMillis()≈ 4× faster and zero allocation (the old path allocates a
GregorianCalendar+ aDateper call).On-device confirmation — ART method trace, Pixel 3 (Android 12), cold start to idle. Same scenario, per-cold-start call counts:
Date)long)SentryNanotimeDateobjects createdDateUtils.getCurrentDateTime()Calendar.getInstance(TimeZone)GregorianCalendar.<init>The eliminated
getCurrentDateTime/Calendarcalls map to the no-argSentryNanotimeDate()constructions — the allocation chain is gone from the timestamp path while the number ofSentryNanotimeDateobjects is unchanged.Average time to construct a⚠️ These are instrumentation-inflated (ART records every method entry/exit) and exaggerate both the absolute value and the ratio — the real per-call figure is the ~56 ns → ~14 ns microbenchmark above. The trace's value here is the call-count evidence that the
SentryNanotimeDate()in the (instrumented) trace was ~372 µs baseline → ~24 µs change.Calendar/GregorianCalendarallocations actually disappear on a real device.Startup time is unaffected (only a handful of these calls during cold start); the win is allocation/GC reduction that scales with tracing volume.
💚 How did you test it?
Updated existing unit tests for the new constructor signature; full
sentryandsentry-android-coretest suites pass. Behavior verified equivalent (epoch-millis identical to the previousDate-based path).📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
Other internal callers already migrated to the
longconstructor. Hybrid SDKs that constructedSentryNanotimeDatedirectly should adopt the new signature (or switch toSentryLongDate); the class is now@ApiStatus.Internal.