← All posts
Android6 August 20267 min read

Room migrations I got wrong so you don't have to

Four migrations, three production incidents, and the checklist I use now.

Four migrations, three production incidents. Here is the checklist I use now.

What went wrong, in order

  1. Added a non-null column with no default. Every existing row failed.
  2. Renamed a column by dropping and recreating. Deleted everyone's data.
  3. Bumped the version and forgot the migration entirely, so fallbackToDestructiveMigration quietly wiped the table.

That third one is the dangerous one, because it does not crash. It just loses everything and carries on.

The rule I follow now

val MIGRATION_3_4 = object : Migration(3, 4) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE puzzle ADD COLUMN streak INTEGER NOT NULL DEFAULT 0")
    }
}

Every added column gets a default. Every rename is a create-copy-drop, written out by hand. fallbackToDestructiveMigration never ships in a release build.

Test it against a real old database

Room's MigrationTestHelper will run a migration against a schema you exported earlier. Export every schema, commit them, and write the test. It takes ten minutes and it is the only thing that catches the case where you migrate a database you no longer have on your machine.

Written by Sulton UzDev. Get new posts by email →

Sulton UzDev
Senior Android Developer · Kotlin, Compose & KMP · I build the app and its backend
© 2026 Sulton UzDevUzbekistanBlogProjectsAboutSubscribe