Room migrations

Data and storage Mid-level Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Order the steps

Order the steps for adding a pinned column safely.

  • Write a MigrationTestHelper test from 3 to 4
  • Build to export the new schema JSON and commit it
  • Add val pinned: Boolean = false with @ColumnInfo(defaultValue = "0")
  • Ship
  • Bump version from 3 to 4 and add AutoMigration(from = 3, to = 4)

Choose an answer, then open Show answer below to check it.

Show answer

Answer: C E B A D

  1. Add val pinned: Boolean = false with @ColumnInfo(defaultValue = "0") A default makes existing rows valid.
  2. Bump version from 3 to 4 and add AutoMigration(from = 3, to = 4) Room generates the ALTER TABLE.
  3. Build to export the new schema JSON and commit it Auto-migrations and tests read these files.
  4. Write a MigrationTestHelper test from 3 to 4 Proves real old data survives.
  5. Ship Only after the migration test passes.

30-second answer

Bump the database version and provide a migration from each old version. For simple changes like adding a column or table, @AutoMigration(from = 1, to = 2) generates it from the exported schemas; for renames or deletes add an AutoMigrationSpec. For complex changes write a Migration with SQL. Test every path with MigrationTestHelper against the exported schema files.

Practise saying it · 35 s. No recording, just a timer

Explain Room migrations.

Say your answer out loud, then check which of these points you covered:

  • Bump the version
  • AutoMigration for simple changes
  • Manual Migration for complex ones
  • Test with MigrationTestHelper
  • Destructive fallback only for caches

Deep dive

fallbackToDestructiveMigration() deletes all data when a migration is missing; use it only for caches you can rebuild.

Users can jump several versions, so test 1→3 as well as 2→3.

Adding a NOT NULL column needs a default value.

Keep the schema JSON files in version control; auto-migrations need them.

An auto-migration Compile-checked · Kotlin 2.4.21
@Entity data class Task(
    @PrimaryKey val id: Long,
    val title: String,
    @ColumnInfo(defaultValue = "0") val pinned: Boolean = false,
)

@Database(
    entities = [Task::class],
    version = 4,
    autoMigrations = [AutoMigration(from = 3, to = 4)],
)
abstract class TaskDb : RoomDatabase()

Common mistakes

  • Shipping a version bump without a migration and crashing on update.
  • Using destructive fallback for user data.
  • Testing only the latest migration.

Senior take & follow-ups

Mention schema export and migration tests; that's the senior part.

Follow-up questions

Think of your answer first, then open the hint.

  1. What if users skip versions?

    Show hint

    Room chains migrations, but test the full path.

  2. When is destructive fallback acceptable?

    Show hint

    For a pure cache that can be refetched.

Compare on iOS: Rolling out a breaking schema change Core Data and SwiftData migrations solve the same problem.

Suggest a fix if something here is wrong or out of date.