Non-Fatal Error — is an error that does not terminate the application and allows program execution to continue. Unlike a fatal error, non-fatal errors can be caught, handled, and logged without losing the user session. According to Firebase Crashlytics Documentation, 2024, about 70% of all logged errors in production applications are non-fatal, but ignoring them leads to accumulated technical debt and gradual degradation of user experience. Proper handling of non-fatal errors is one of the key skills of a mobile developer.
Key Takeaways
Non-Fatal Error — is an exception or error state that does not cause process termination. The application continues running, but may be in an incorrect state: data failed to load, a request was not sent, a UI element did not render. The user either does not notice the error or sees a message and continues using the application.
A non-fatal error always leaves the program a path to recovery. An error handler can provide alternative data, retry the operation, or show a UI placeholder. The main goal is to prevent a crash and maintain an acceptable user experience. The developer must explicitly plan a recovery scenario in each catch block.
According to Instabug 2024, 65% of users uninstall an app after two unsuccessful interactions. Non-fatal errors left unattended accumulate and degrade overall quality. Systematic logging and fixing of non-fatal errors is a direct path to improving retention and boosting app store ratings.
Network errors are the most common type of non-fatal errors in mobile applications. Connection timeout, network loss, incorrect server status code — all these situations are caught and handled without a crash. The user is shown a service unavailability message with an option to retry. The retry pattern with exponential backoff is typical for network errors.
Incorrect server response format, missing required fields, invalid data type — parsing errors are non-fatal if the application correctly handles malformed data. The typical approach is to use default fallback values and log the parsing error with request context for later server-side analysis.
Image loading issues, incorrect fonts, layout errors — all are non-fatal but degrade the user experience. Placeholder images and fallback values help avoid empty screens and make errors less noticeable. In React Native, Error Boundary is used for UI errors with a fallback component displayed.
Calculation errors, state mismatches, incorrect screen transitions — logic errors often do not cause a crash but lead to incorrect application behavior. They are harder to detect without systematic logging and monitoring because they do not generate a crash report and remain unnoticed until a user complaint.
Non-Fatal Error differs from fatal in that it leaves the program a chance to continue working. A fatal error is a state from which the application cannot recover: null pointer dereference, stack overflow, out of memory. A non-fatal error can be caught, handled, and execution can continue, whereas a fatal error requires restarting the application.
| Characteristic | Non-Fatal Error | Fatal Error |
|---|---|---|
| App Termination | No | Yes |
| Recovery Possible | Yes, via catch block | No |
| Logging | From code via recordException | Only by crash reporter |
| UX Impact | Temporary inconvenience | Complete session failure |
| Example | Network timeout, parse error | NullPointerException, OOM |
The boundary between non-fatal and fatal can depend on implementation. A network timeout in one application is handled as non-fatal (retry after 1–2 seconds), while in another it may be fatal (crash if no handler exists). Quality error handling turns potentially fatal situations into non-fatal ones, increasing application stability. Designing an error handling system is one of the key architectural tasks when developing a mobile application with high reliability requirements. A built-in monitoring system allows the team to quickly detect and fix non-fatal errors before they affect a significant number of users.
Firebase Crashlytics is the primary tool for logging non-fatal errors in mobile applications. The recordException method allows you to capture a non-fatal exception with a full stack trace and execution context without interrupting the application. Unlike crash reports, recordException can be called anywhere in the code to log caught exceptions.
fun fetchUserData(userId: String) {
try {
val response = apiService.getUser(userId)
updateUI(response)
} catch (e: IOException) {
Crashlytics.log("Network error for user $userId")
Crashlytics.recordException(e)
showRetryDialog()
} catch (e: JsonParseException) {
// Non-fatal: using fallback data
Crashlytics.recordException(e)
showFallbackContent()
}
}
// Logging with custom keys
Crashlytics.setCustomKey("screen", "Profile")
Crashlytics.setCustomKey("api_version", "v3")
Sentry is an alternative to Crashlytics with more detailed diagnostics for non-fatal errors. The Sentry SDK provides the captureException method, which sends exception details to the server. Sentry’s key advantage is grouping similar non-fatal errors into a single issue, analyzing recurrence frequency, and providing execution context as breadcrumbs — the sequence of user actions before the error.
Not all non-fatal errors need to be logged. Expected states — network failure when there is no connection — can be logged selectively. Unexpected errors — NullPointerException in handled code, invalid data format, logic errors — should always be logged. Each team defines its significance threshold: on average, 10 to 20 unique non-fatal errors per 1000 users per day is considered normal. It is important to set up alerts for a sharp increase in non-fatal errors — this may indicate problems with a new API version or a regression after a release.
The basic handling mechanism is try-catch, which catches the exception and executes recovery code. For network operations, the typical pattern is retry with exponential backoff. For parsing errors, the approach is to use default fallback values and log the context for later server-side analysis.
func loadImage(from url: URL) -> UIImage? {
do {
let data = try Data(contentsOf: url)
return UIImage(data: data)
} catch {
Logger.shared.logError(error: "Image load failed: \(url)")
return UIImage(named: "placeholder")
}
}
func performRequest() async throws -> Data {
var lastError: Error? = nil
for attempt in 0..<3 {
do {
return try await URLSession.shared.data(from: url)
} catch {
lastError = error
try await Task.sleep(UInt64(pow(2, attempt)) * 1_000_000_000)
}
}
throw lastError ?? URLError(.unknown)
}
Result types — an alternative approach without exceptions. A function returns a sealed class Result with Success and Failure variants. The calling code explicitly handles both variants, eliminating unhandled errors. Result types are popular in Kotlin (Result
For each type of non-fatal error, a recovery strategy should be planned: loading cached data on a network error, using default values on a parsing error, reinitializing a component on a UI error. A good practice is to show the user a toast or snackbar with an error message without blocking interaction with the application entirely. It is important to distinguish between recoverable and non-recoverable errors — for the latter, the recovery strategy will be different, such as suggesting a screen restart or clearing data. Caching the previous successful state is often the simplest and most effective way to handle non-fatal errors on mobile platforms.
Frequently Asked Questions
Warning is a compiler or static analyzer warning about a potential problem in the code. A non-fatal error is a runtime exception that has already occurred but did not cause a crash. A warning can be fixed before compilation; a non-fatal error must be handled at runtime via a catch block.
No, excessive logging clutters monitoring. Unexpected errors in production should be logged, while expected states should be ignored: network failure when offline can be logged selectively, but a NullPointerException in handled code should always be logged. Each team defines its significance threshold based on the application context.
In SwiftUI, ObservableObject with an @Published errorState field is used to track the error state. The view subscribes to changes and displays alternative content. Before iOS 17, Combine with handlers was used; starting with iOS 17, SwiftData and @Observable macros are used for reactive UI updates.
Yes, if the error triggers a chain reaction. Example: a non-fatal image loading failure can lead to an incorrect UI state, which then causes a crash when attempting to display. Quality handling of non-fatal errors at every level prevents their escalation to a fatal level.
In iOS, non-fatal errors are handled via do-catch with throw; in Android, via try-catch with exceptions. iOS uses NSError with domains and error codes; Android uses Java/Kotlin exceptions. Crashlytics works identically on both platforms via recordException, providing a unified monitoring interface.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also