Recover a corrupt Android app SQLite database

Android apps keep their state in SQLite, so a crash-on-launch is usually one broken page or pointer in a .db file — not lost data. Room and SQLiteOpenHelper write a standard SQLite 3 database, and the rows are almost always still sitting in their pages. Rebuilding them from a copy you pulled off the device never means handing that database to a server.

Your file never leaves your device — repair runs in your browser. 0 bytes uploaded.

Your data is the big block — usually intact. What breaks is the small index. Repair rebuilds it.

Drop a file here or browseTap to choose a file

ZIP · Office · PDF · video · JPG · PNG · RAR/7z · SQLite — repaired right here in your browser. Nothing is uploaded

0 bytes uploadedFree: 3 repairs/day — up to 500 MB video, 100 MB docs & archives, 50 MB photos. Free download. Single repair €5.90. No account required.

Fix it now

  1. Drop the file
  2. Repair runs locally
  3. Download the result

Both of Android's persistence layers write ordinary SQLite: Room generates its schema on top of the same engine the old SQLiteOpenHelper uses, and both land a single file at /data/data/<package>/databases/<name>.db. When that file is damaged, Room throws android.database.sqlite.SQLiteDatabaseCorruptException: database disk image is malformed (code 11 SQLITE_CORRUPT) and the app force-closes or shows an empty screen. Drop the pulled .db above — and add its -wal sidecar in the optional slot if you have it — and the tool scans the pages, rebuilds the schema, and re-inserts every row it can still decode into a fresh database, all inside your browser tab. The file is never uploaded.

Where an Android app keeps its database — and what's inside it

On any Android device the database lives in the app's private sandbox at /data/data/<package>/databases/, usually as three files: the main <name>.db, a write-ahead log <name>.db-wal, and a shared-memory index <name>.db-shm. The main file opens with the 16-byte magic string "SQLite format 3\000"; the two-byte page size sits at byte offset 16, and the file-format read/write version bytes at offsets 18 and 19 read 2 when the database is in WAL mode — which Room turns on by default.

Room adds fingerprints you can use to confirm you have the right file. It creates an android_metadata table holding a single locale row (for example en_US) — that table is written by Android's SQLiteDatabase for every app DB, Room or not — and a room_master_table with columns id and identity_hash, where Room stores the schema hash at the fixed row id = 42. Your entity tables (annotated @Entity) and their indices sit alongside them, all listed in the schema table sqlite_master (aliased sqlite_schema in newer SQLite). A plain SQLiteOpenHelper app skips room_master_table but is otherwise the same standard SQLite file.

How the file goes malformed — and the exact errors

A SQLite database is a flat array of fixed-size pages (4096 bytes by default). Page 1 carries the header and the schema; every table and index is a b-tree whose interior pages hold cell pointers down to leaf pages, and the leaves hold your actual records. The engine reads the header, finds the schema in sqlite_master, then follows those pointers to the rows.

The pointers are the weak point. When the app process is killed by Android's low-memory killer mid-write, the phone loses power, storage fills up, or two processes open the same file without proper locking, a single interior page or a header field can be left inconsistent. SQLite then stops at the first problem and refuses the whole file with database disk image is malformed — result code 11 (SQLITE_CORRUPT) — even though the leaf pages behind the break still hold intact, self-describing records. A related failure, file is not a database — result code 26 (SQLITE_NOTADB) — means the 16-byte header magic is wrong: either the header bytes were damaged (the pages behind them are often fine and recoverable) or the file is genuinely something else, such as an SQLCipher-encrypted database that reads as high-entropy noise.

One Android-specific trap makes this urgent: the framework's DefaultDatabaseErrorHandler.onCorruption() deletes the database file when it detects corruption on open. So an app that hit the malformed error may already have wiped the file on its next launch. Copy the database off the device — and work on that copy — before opening the app again.

The -wal and -shm sidecars: pull all three

Room enables write-ahead logging by default (enableWriteAheadLogging()), which means recent committed changes are staged in the sibling <name>.db-wal file until a checkpoint folds them back into the main .db. After a crash, your newest rows can live only in that WAL. Pull the main file without it and the recovered database will be missing everything written since the last checkpoint — a common reason people think "half my data is gone".

The WAL is not the main database: it starts with a 32-byte header whose magic is 0x377f0682 or 0x377f0683 (the low bit selects big- or little-endian frame checksums), followed by frames of a 24-byte frame header plus one page image each. The -shm file is just a shared-memory index into the WAL; it is regenerated automatically and safe to leave behind. So pull the main .db and the -wal together: drop the database above, then add the -wal in the optional sidecar slot and its committed frames are overlaid before the scan, so recovery reflects the last committed transaction rather than the last checkpoint.

Getting the database off the device safely

On a debuggable build you can reach the private folder with run-as: for example adb exec-out run-as <package> tar c ./databases | tar xv, or copy the files to shared storage with run-as <package> cp databases/<name>.db /sdcard/ and then adb pull. Release (non-debuggable) apps refuse run-as, so you need a rooted device or the app's own export feature. The old adb backup -f backup.ab <package> path was deprecated in Android 12 and honours android:allowBackup, so it is unreliable for this. Whichever route you take, grab <name>.db, <name>.db-wal and <name>.db-shm in one go so the WAL matches the main file.

With the copy on your machine, the classic check is sqlite3 name.db "PRAGMA integrity_check;" (or the faster quick_check), and the classic fix is SQLite's own .recover command, which reads out every decodable row and writes it into a brand-new file. This page does exactly that in the browser: it walks the pages, rebuilds the b-trees, falls back to a raw page sweep for rows the pointer chain can no longer reach, and drops anything it cannot attribute to a known table into a lost_and_found table rather than discarding it — so nothing is thrown away and nothing is uploaded.

What this can and can't fix

Can fix

  • "database disk image is malformed (code 11 SQLITE_CORRUPT)" from a damaged page or b-tree cell pointer, with intact leaf pages behind it
  • A wrong or damaged header (bad page-size or page-count field) when the pages after it are still readable
  • Rows on pages the pointer chain can no longer reach, pulled out by a raw page scan into a lost_and_found table
  • Uncheckpointed rows from a supplied .db-wal sidecar (the newest data after a crash)
  • Room and SQLiteOpenHelper databases copied off the device before the app's error handler deleted them

Can't fix

  • Rows physically overwritten or truncated off the end of the file — those bytes no longer exist
  • SQLCipher-encrypted databases (Signal, or Room apps using a SupportFactory) without the passphrase — the pages are ciphertext
  • A database that DefaultDatabaseErrorHandler already deleted — that's a device data-recovery problem first
  • Exact column affinities: values come back as their on-disk storage class (NULL/INTEGER/REAL/TEXT/BLOB)
  • A 0-byte .db file — there is nothing on disk to decode

If a repair fails, we tell you why (missing data versus broken structure), and you are never charged for a failed repair.

FAQ

My app deleted its own database after the crash — why?

Android's DefaultDatabaseErrorHandler.onCorruption() deletes the file when it detects SQLITE_CORRUPT on open, so a second launch can wipe it. That's why the fix is to copy the .db (and its -wal) off the device first with adb run-as and recover from the copy — never from the live file the app is still opening.

I pulled the .db but recent data is missing. What happened?

Room runs in WAL mode by default, so the newest committed rows are in <name>.db-wal, not yet folded into the main file. Pull all three files — .db, -wal and -shm — and add the -wal in the optional sidecar slot; its committed frames are overlaid before the scan so the last transaction is included.

Does this work on a Room database, or only raw SQLite?

Both — Room is plain SQLite underneath. Recovery rebuilds android_metadata, room_master_table (including the identity_hash stored at id = 42) and your @Entity tables. If the schema hash comes through intact, Room reopens the rebuilt file without complaining that it cannot verify data integrity.

The error says 'file is not a database' instead. Is that recoverable?

Sometimes. That is SQLITE_NOTADB (code 26): the 16-byte header magic is wrong. If only the header bytes were damaged, the pages behind them are usually intact and recoverable. If the whole file is high-entropy noise it is an encrypted (SQLCipher) database or not a database at all — that can't be recovered without the key. The diagnosis tells the two apart.

Is my database uploaded to be repaired?

No. An app database is the single file you'd least want on someone else's server — it can hold every user record, message and token the app stored. Here the .db is read from your disk and rebuilt in your browser; you can watch the Network tab and confirm 0 bytes leave.

Related: Repair a SQLite database (any source) · Repair an Android video · Repair an Excel workbook · Verify the zero-upload claim