When a crash file ([CLY]_crash_*) exists in the storage directory, every subsequent call to Countly.instance().init(config) throws a NullPointerException because SDKCore.recover() is invoked before the networking field is assigned.
The crash file is never cleaned up after the exception (the deletion line comes after the NPE), so the bug repeats on every application restart.
This is a critical bug. Once a single crash is recorded, the SDK becomes permanently non-functional for that storage directory. All events, sessions, and future crashes accumulate on disk indefinitely. The only recovery is manually deleting the crash file from the filesystem.
Root Cause
In SDKCore.init(InternalConfig), the call order is:
// SDKCore.java — init()
recover(config); // internally using networking
networking = new DefaultNetworking(); // networking assigned AFTER
recover() iterates stored crash files and calls processCrash(), which eventually calls Storage.push() → networking.check(config). Since networking is still null at this point:
java.lang.NullPointerException: Cannot invoke "ly.count.sdk.java.internal.Networking.check(ly.count.sdk.java.internal.InternalConfig)" because "this.networking" is null
at ly.count.sdk.java.internal.SDKCore.onSignal(SDKCore.java:682)
at ly.count.sdk.java.internal.ModuleRequests.injectParams(ModuleRequests.java:38)
at ly.count.sdk.java.internal.Storage.pushAsync(Storage.java:218)
at ly.count.sdk.java.internal.SDKCore.onCrash(SDKCore.java:455)
at ly.count.sdk.java.internal.CrashImpl.processCrash(CrashImpl.java:93)
at ly.count.sdk.java.internal.SDKCore.recover(SDKCore.java:493)
at ly.count.sdk.java.internal.SDKCore.init(SDKCore.java:126)
Steps to Reproduce
- Initialize the Countly SDK with crash reporting enabled
- Trigger a crash that gets recorded as a [CLY]_crash_* file in the storage directory
- Restart the application and call Countly.instance().init(config) again
- SDK init throws NPE every time — the crash file is never sent or removed
Expected Behavior
recover() should execute after networking is initialized, or processCrash() should handle a null networking field gracefully. The crash file should be cleaned up even if processing fails, to avoid a permanent init-blocking loop.
When a crash file ([CLY]_crash_*) exists in the storage directory, every subsequent call to
Countly.instance().init(config)throws a NullPointerException becauseSDKCore.recover()is invoked before the networking field is assigned.The crash file is never cleaned up after the exception (the deletion line comes after the NPE), so the bug repeats on every application restart.
This is a critical bug. Once a single crash is recorded, the SDK becomes permanently non-functional for that storage directory. All events, sessions, and future crashes accumulate on disk indefinitely. The only recovery is manually deleting the crash file from the filesystem.
Root Cause
In
SDKCore.init(InternalConfig), the call order is:recover()iterates stored crash files and callsprocessCrash(), which eventually callsStorage.push()→networking.check(config). Since networking is still null at this point:Steps to Reproduce
Expected Behavior
recover()should execute after networking is initialized, orprocessCrash()should handle a null networking field gracefully. The crash file should be cleaned up even if processing fails, to avoid a permanent init-blocking loop.