---
# === IDENTITY ===
id: software/debugging/android-crash-patterns/2026
canonical_question: "What are the most common Android app crash patterns and fixes?"
aliases:
  - "Why does my Android app keep crashing?"
  - "How to debug NullPointerException on Android?"
  - "Android ANR causes and solutions"
  - "How to fix OutOfMemoryError in Android apps?"
entity_type: software_reference
domain: software > debugging > android_crash_patterns
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.92
version: 1.0
first_published: 2026-02-23

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Android 14 (API 34) behavior changes, 2023-10"
  next_review: 2026-08-22
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Native crashes (SIGSEGV/SIGABRT) require debug symbols — release builds strip symbols by default"
  - "ANR thresholds are enforced by the OS: 5s for input dispatch, ~20s for broadcast receivers in background"
  - "Never catch Throwable or Error in production — only catch specific Exception subclasses"
  - "Google Play flags apps exceeding 1.09% user-perceived crash rate, affecting discoverability"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Crash is in a third-party SDK or library you do not control"
    use_instead: "Contact the SDK vendor; use Crashlytics breadcrumbs to isolate SDK frames"
  - condition: "Crash only occurs on rooted/modified devices"
    use_instead: "Device-specific issues — check SafetyNet/Play Integrity attestation first"

# === AGENT HINTS ===
inputs_needed:
  - key: crash_type
    question: "Is this a Java/Kotlin exception, a native (NDK) crash, or an ANR?"
    type: choice
    options: ["java_kotlin_exception", "native_ndk_crash", "anr"]
  - key: stack_trace
    question: "Can you paste the full stack trace or logcat output?"
    type: text

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/android-crash-patterns/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to: []
  solves: []
  alternative_to: []
  often_confused_with: []

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Crashes | App quality | Android Developers"
    author: Google
    url: https://developer.android.com/topic/performance/vitals/crash
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src2
    title: "ANRs | App quality | Android Developers"
    author: Google
    url: https://developer.android.com/topic/performance/vitals/anr
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src3
    title: "Diagnose native crashes | Android Open Source Project"
    author: Google
    url: https://source.android.com/docs/core/tests/debug/native-crash
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src4
    title: "Debugging native crashes on Android just got easier with Crashlytics"
    author: Firebase
    url: https://firebase.blog/posts/2025/11/crashlytics-android-ndk-tombstones
    type: technical_blog
    published: 2025-11-15
    reliability: high
  - id: src5
    title: "Managing Bitmap Memory | Android Developers"
    author: Google
    url: https://developer.android.com/topic/performance/graphics/manage-memory
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src6
    title: "Fragment lifecycle | Android Developers"
    author: Google
    url: https://developer.android.com/guide/fragments/lifecycle
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src7
    title: "The Anatomy of an Android Crash: From Exception to Stack Trace"
    author: Bugsee
    url: https://bugsee.com/blog/the-anatomy-of-an-android-crash-from-exception-to-stack-trace/
    type: technical_blog
    published: 2025-03-10
    reliability: moderate_high
---

# Android App Crash Patterns and Fixes

## TL;DR

- **Bottom line**: Most Android crashes fall into ~12 repeatable patterns; NullPointerException alone causes ~30-40% of all Java/Kotlin crashes, followed by ANRs from main-thread blocking, OOM from bitmap mismanagement, and IllegalStateException from fragment lifecycle violations.
- **Key tool/command**: `adb logcat *:E | grep -E "FATAL|ANR|Exception"` to capture crash traces in real time.
- **Watch out for**: Catching generic `Exception` or `Throwable` — this masks root causes and creates silent data corruption.
- **Works with**: Android 5.0+ (API 21+), Kotlin 1.5+, Java 8+, Android Studio 2020.3+.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Native crashes (SIGSEGV/SIGABRT) require debug symbols — release builds strip symbols by default. Always upload native debug symbols to Play Console or Crashlytics.
- ANR thresholds are enforced by the OS: 5 seconds for input dispatch, ~5 seconds for foreground broadcast receivers, ~20 seconds for background receivers. These cannot be configured.
- Never catch `Throwable` or `Error` in production — only catch specific `Exception` subclasses. Catching `OutOfMemoryError` leads to undefined behavior.
- Google Play flags apps exceeding 1.09% user-perceived crash rate, directly impacting search ranking and app discoverability.
- `TransactionTooLargeException` has a hard 1MB Binder buffer limit per process — this cannot be increased.

## Quick Reference

| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | NullPointerException | ~35% of crashes | `java.lang.NullPointerException: Attempt to invoke virtual method '...' on a null object reference` | Use Kotlin null safety (`?.`, `?:`, `!!` only when guaranteed); annotate Java with `@NonNull`/`@Nullable` [src1] |
| 2 | ANR — main thread I/O | ~15% of crashes | `ANR in com.example.app` + `main` thread in `WAITING` or `BLOCKED` state | Move all disk/network I/O to coroutines (`Dispatchers.IO`) or `WorkManager` [src2] |
| 3 | OutOfMemoryError | ~10% of crashes | `java.lang.OutOfMemoryError: Failed to allocate a N-byte allocation` | Use `Glide`/`Coil` for image loading; set `BitmapFactory.Options.inSampleSize`; avoid storing bitmaps in static fields [src5] |
| 4 | IllegalStateException (Fragment) | ~8% of crashes | `java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState` | Use `commitAllowingStateLoss()` or check `lifecycle.currentState.isAtLeast(STARTED)` before fragment transactions [src6] |
| 5 | SecurityException | ~6% of crashes | `java.lang.SecurityException: Permission Denial: ... requires android.permission.X` | Check `ContextCompat.checkSelfPermission()` before calling protected APIs; handle denial gracefully |
| 6 | SIGSEGV (native) | ~5% of crashes | `signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0` | Use AddressSanitizer (ASan) during development; upload native debug symbols; check for use-after-free [src3] |
| 7 | TransactionTooLargeException | ~4% of crashes | `android.os.TransactionTooLargeException: data parcel size N bytes` | Keep `onSaveInstanceState` bundles < 500KB; use `ViewModel` for large state; avoid large Intent extras |
| 8 | IndexOutOfBoundsException | ~4% of crashes | `java.lang.IndexOutOfBoundsException: Index: N, Size: M` | Validate list sizes before access; use `getOrNull()` in Kotlin; synchronize concurrent list modifications |
| 9 | DeadObjectException | ~3% of crashes | `android.os.DeadObjectException` at IPC call site | Wrap Binder calls in try/catch; reconnect to service on failure; use `ServiceConnection.onServiceDisconnected()` callback |
| 10 | ClassCastException | ~2% of crashes | `java.lang.ClassCastException: X cannot be cast to Y` | Use `is` checks before casting; prefer generics; avoid raw `Bundle.get()` — use typed `getInt()`, `getString()` |
| 11 | SIGABRT (native) | ~2% of crashes | `signal 6 (SIGABRT), code -6 (SI_TKILL)` with `abort message: 'FORTIFY: ...'` | Fix buffer overflows; check `__stack_chk_fail` for stack corruption; review `assert()` conditions [src3] |
| 12 | ConcurrentModificationException | ~2% of crashes | `java.util.ConcurrentModificationException` during iteration | Use `CopyOnWriteArrayList`, `synchronized` blocks, or `toMutableList()` copy before modification |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (27 lines)

```
START
├── Is it an ANR (dialog says "App isn't responding")?
│   ├── YES → Check main thread state in traces.txt
│   │   ├── Main thread BLOCKED on I/O → Move to Dispatchers.IO (Cause #2)
│   │   ├── Main thread WAITING on lock → Fix lock contention or deadlock
# ... (see full script)
```

## Step-by-Step Guide

### 1. Capture the crash trace

Connect the device via USB and start logcat with crash filtering. [src1]

```bash
# Filter for fatal crashes and ANRs
adb logcat *:E | grep -E "FATAL EXCEPTION|ANR in|signal [0-9]+"

# Or save full logcat to file for analysis
adb logcat -d > crash_log.txt
```

**Verify**: `adb logcat -d | grep "FATAL EXCEPTION"` should show the crash with full stack trace, thread name, and PID.

### 2. Identify the crash category

Parse the first line of the exception to determine the crash type. Match it against the Quick Reference table. [src7]

```
# Java/Kotlin crash format:
FATAL EXCEPTION: main
Process: com.example.app, PID: 12345
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
    at com.example.app.MainActivity.processData(MainActivity.kt:42)
    at com.example.app.MainActivity.onCreate(MainActivity.kt:18)

# Native crash format:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 12345, tid: 12345, name: main  >>> com.example.app <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
```

**Verify**: You can identify the exception class, the exact file and line number, and the thread name from the trace.

### 3. Check if it's lifecycle-related

For crashes involving fragments, activities, or configuration changes, verify the component lifecycle state. [src6]

```kotlin
// Add lifecycle logging to narrow down timing
class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d("Lifecycle", "onViewCreated: ${lifecycle.currentState}")

        viewLifecycleOwner.lifecycleScope.launch {
            // Safe: automatically cancelled when view is destroyed
            val data = withContext(Dispatchers.IO) { repository.fetchData() }
            // Check lifecycle before UI update
            if (viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
                binding.textView.text = data
            }
        }
    }
}
```

**Verify**: `adb logcat | grep "Lifecycle"` shows state transitions — crash should not occur after `DESTROYED`.

### 4. Profile memory for OOM crashes

Use Android Studio Profiler to capture heap dumps and track allocations. [src5]

```bash
# Dump heap from command line
adb shell am dumpheap com.example.app /data/local/tmp/heap.hprof
adb pull /data/local/tmp/heap.hprof

# Check process memory limits
adb shell getprop dalvik.vm.heapsize
# Typical output: 256m or 512m depending on device
```

**Verify**: Open the `.hprof` file in Android Studio Memory Profiler. Look for retained objects with large shallow sizes, especially `Bitmap` and `byte[]` allocations.

### 5. Retrieve ANR traces

Pull the ANR trace files to analyze which thread is blocked. [src2]

```bash
# List ANR trace files
adb shell ls /data/anr/

# Pull the most recent trace
adb pull /data/anr/traces.txt

# On Android 11+, use ApplicationExitInfo
adb shell dumpsys activity exit-info com.example.app
```

**Verify**: In `traces.txt`, find the `main` thread — its state (`BLOCKED`, `WAITING`, `TIMED_WAITING`) reveals the ANR cause.

### 6. Symbolicate native crashes

Use ndk-stack to convert raw addresses to source file and line numbers. [src3]

```bash
# Symbolicate from logcat output
adb logcat | ndk-stack -sym path/to/obj/local/arm64-v8a/

# Symbolicate from tombstone file
adb pull /data/tombstones/tombstone_00
ndk-stack -sym path/to/obj/local/arm64-v8a/ -dump tombstone_00

# Or use addr2line for individual addresses
$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-addr2line \
  -f -e path/to/libexample.so 0x42f89
```

**Verify**: Output should show source file paths and line numbers instead of raw hex addresses.

## Code Examples

### Kotlin: Safe null handling patterns

```kotlin
// Input:  Nullable data from Intent extras, Bundle, or Java interop
// Output: Crash-free access with sensible defaults

// Pattern 1: Safe call + Elvis for default value
val userName: String = intent.getStringExtra("user_name") ?: "Guest"

// Pattern 2: let-block for conditional execution
intent.getStringExtra("deep_link")?.let { url ->
    navigator.handleDeepLink(url)  // Only executes if non-null
}

// Pattern 3: require/check for fail-fast with clear messages
fun processOrder(orderId: String?) {
    requireNotNull(orderId) { "orderId must not be null — check Intent extras" }
    // orderId is smart-cast to non-null String here
    repository.loadOrder(orderId)
}
```

### Kotlin: Coroutine-safe lifecycle-aware operations

```kotlin
// Input:  Async operations that outlive the Activity/Fragment
// Output: Automatic cancellation, no IllegalStateException

class SearchFragment : Fragment(R.layout.fragment_search) {
    private val viewModel: SearchViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // viewLifecycleOwner scope — auto-cancelled when view destroyed
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.results.collectLatest { results ->
                adapter.submitList(results)
            }
        }

        // repeatOnLifecycle — restarts when STARTED, cancels when STOPPED
        viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    updateUI(state)  // Safe: only runs when fragment is visible
                }
            }
        }
    }
}
```

### Java: Defensive permission checking

> Full script: [java-defensive-permission-checking.java](scripts/java-defensive-permission-checking.java) (25 lines)

```java
// Input:  Runtime permission request before camera access
// Output: Graceful handling without SecurityException crash
private static final int REQUEST_CAMERA = 100;
private void openCamera() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
# ... (see full script)
```

### Java: Safe bitmap loading to prevent OOM

> Full script: [java-safe-bitmap-loading-to-prevent-oom.java](scripts/java-safe-bitmap-loading-to-prevent-oom.java) (30 lines)

```java
// Input:  Large image file path or resource
// Output: Downsampled bitmap that fits in memory
public static Bitmap decodeSampledBitmap(String filePath,
        int reqWidth, int reqHeight) {
    // Step 1: Read dimensions only (no pixel allocation)
# ... (see full script)
```

## Anti-Patterns

### Wrong: Catching generic Exception to suppress crashes

```kotlin
// BAD — hides real bugs, causes silent data corruption
try {
    val result = processPayment(order)
    updateUI(result)
} catch (e: Exception) {
    // Swallowed — no logging, no user feedback, no crash report
}
```

### Correct: Catch specific exceptions with proper handling

```kotlin
// GOOD — catches expected failures, lets real bugs surface
try {
    val result = processPayment(order)
    updateUI(result)
} catch (e: IOException) {
    Log.w(TAG, "Network error during payment", e)
    showRetryDialog()
} catch (e: PaymentDeclinedException) {
    showDeclinedMessage(e.reason)
}
// NullPointerException, IllegalStateException, etc. will still crash
// and appear in Crashlytics — as they should
```

### Wrong: Performing network calls on the main thread

```kotlin
// BAD — causes ANR after 5 seconds
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val response = URL("https://api.example.com/data").readText() // Blocks main thread
    binding.textView.text = response
}
```

### Correct: Use coroutines with appropriate dispatcher

```kotlin
// GOOD — non-blocking, lifecycle-aware
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    lifecycleScope.launch {
        val response = withContext(Dispatchers.IO) {
            URL("https://api.example.com/data").readText()
        }
        binding.textView.text = response  // Back on main thread for UI
    }
}
```

### Wrong: Storing Activity references in static fields

```kotlin
// BAD — leaks the entire Activity, leads to OOM
companion object {
    var currentActivity: Activity? = null  // Never do this
    val cachedBitmap: Bitmap? = null       // Survives config changes = leak
}
```

### Correct: Use ViewModel or WeakReference

```kotlin
// GOOD — ViewModel survives config changes without leaking Activity
class MainViewModel : ViewModel() {
    private val _data = MutableLiveData<String>()
    val data: LiveData<String> = _data

    fun loadData() {
        viewModelScope.launch {
            _data.value = repository.fetchData()
        }
    }
}
// Activity observes without being stored
viewModel.data.observe(this) { text -> binding.textView.text = text }
```

### Wrong: Fragment transaction after onSaveInstanceState

```kotlin
// BAD — crashes with IllegalStateException
fun onDataLoaded(data: Data) {
    // Called from async callback — Activity may already be saved
    supportFragmentManager.beginTransaction()
        .replace(R.id.container, DetailFragment.newInstance(data))
        .commit()  // CRASH if called after onSaveInstanceState
}
```

### Correct: Check lifecycle state before transaction

```kotlin
// GOOD — guards against state loss
fun onDataLoaded(data: Data) {
    if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
        supportFragmentManager.beginTransaction()
            .replace(R.id.container, DetailFragment.newInstance(data))
            .commit()
    } else {
        // Queue for later or use commitAllowingStateLoss() if safe
        pendingFragment = DetailFragment.newInstance(data)
    }
}
```

## Common Pitfalls

- **Platform types from Java interop**: Kotlin treats unannotated Java return values as platform types (`String!`) — these bypass null safety. Fix: Add `@Nullable`/`@NonNull` annotations to all public Java APIs, or treat all Java returns as nullable in Kotlin. [src1]
- **StrictMode not enabled during development**: Many ANR-causing I/O operations on the main thread go undetected until production. Fix: Enable `StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build())` in debug builds. [src2]
- **Missing ProGuard/R8 mapping files**: Crash reports show obfuscated stack traces (`a.b.c.d()`) that are impossible to read. Fix: Upload `mapping.txt` to Play Console and Crashlytics for every release build. [src7]
- **Large savedInstanceState bundles**: Saving `RecyclerView` adapter data or bitmaps in `onSaveInstanceState` triggers `TransactionTooLargeException`. Fix: Store only IDs/keys in bundles; use `ViewModel` or `SavedStateHandle` for large state.
- **Ignoring background crash rate**: Crashes in `Service`, `BroadcastReceiver`, or `WorkManager` tasks don't show ANR dialogs but still count toward Play vitals. Fix: Wrap background entry points in try/catch with Crashlytics logging. [src4]
- **Not testing on low-memory devices**: OOM crashes often only appear on devices with 2-3GB RAM. Fix: Use Android Studio emulator with `-memory 2048` to simulate constrained environments. [src5]
- **Using deprecated AsyncTask**: `AsyncTask` was deprecated in API 30 and has well-known lifecycle leak issues. Fix: Replace with `kotlinx.coroutines` and `lifecycleScope`/`viewModelScope`. [src2]
- **Bitmap.Config.ARGB_8888 for all images**: Uses 4 bytes per pixel when many images only need 2. Fix: Use `Bitmap.Config.RGB_565` for opaque images (2 bytes/pixel, 50% memory savings). [src5]

## Diagnostic Commands

> Full script: [diagnostic-commands.sh](scripts/diagnostic-commands.sh) (26 lines)

```bash
# Capture crash logcat in real time (filter for errors)
adb logcat *:E
# Filter for fatal exceptions only
adb logcat | grep -E "FATAL EXCEPTION|Process.*PID"
# Check ANR traces
# ... (see full script)
```

## Version History & Compatibility

| Android Version | Status | Crash-Related Changes | Migration Notes |
|---|---|---|---|
| Android 15 (API 35) | Preview | Stricter background process limits; new tombstone format improvements | Test background services under new restrictions |
| Android 14 (API 34) | Current | Foreground service type required; stricter implicit intent handling | Add `foregroundServiceType` to manifest; use explicit intents |
| Android 13 (API 33) | Supported | Runtime notification permission; per-app language support | Add `POST_NOTIFICATIONS` permission request flow |
| Android 12 (API 31) | Supported | Tombstone collection via Crashlytics NDK; strict exported flag required | Set `android:exported` on all intent-filter components |
| Android 11 (API 30) | Supported | `ApplicationExitInfo` API for programmatic crash analysis; AsyncTask deprecated | Use `getHistoricalProcessExitReasons()` for crash analysis |
| Android 10 (API 29) | Maintenance | Scoped storage introduced; background activity launch restrictions | Update file access patterns; use `PendingIntent` for background launches |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Debugging production crash reports from Crashlytics/Play Console | Crash is a known SDK bug with a pending fix | Wait for SDK update; add workaround in the meantime |
| Setting up crash prevention patterns in a new Android project | You need to fix a build error or compilation failure | Standard build error debugging (not a runtime crash) |
| Profiling ANR rates during performance optimization | The issue is slow UI rendering (jank) without actual ANR | Use Android GPU Inspector or Frame Profiler |
| Investigating OOM on low-memory devices | Memory usage is high but app never actually crashes | Use Memory Profiler for optimization, not crash-focused debugging |
| Training team on crash-free coding practices | The crash only occurs in unit tests, not on device | Check test framework configuration and mocking setup |

## Important Caveats

- Crash-free rate thresholds differ between Google Play (1.09% for user-perceived crashes) and internal quality bars — most top apps target < 0.5%.
- NullPointerException statistics vary by codebase: pure Kotlin apps see ~30% fewer NPEs than mixed Java/Kotlin codebases (per Google Home team migration data). [src1]
- ANR trace files (`/data/anr/traces.txt`) may be overwritten by subsequent ANRs — pull them immediately after reproduction.
- Native crash debugging requires matching debug symbols for the exact build — symbol files from a different commit will produce wrong line numbers.
- `TransactionTooLargeException` is only thrown on Android 7.0+; on earlier versions the transaction silently fails, which is harder to debug.

## Related Units
<!-- Generated from related_kos frontmatter -->

- No related units yet — this is a foundational reference.
