Androidでの「Room Cannot Verify Data Integrity」エラーの修正方法

intermediate📱 Android2026-07-18| Android Studio, Room Persistence Library (2.x 以上), Kotlin/Java, SQLite.

Error Message

java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
#room#android-development#sqlite#kotlin#database-migration

想定されるシナリオ

Userエンティティにprofile_picture_urlカラムを追加したとします。「実行」ボタンを押し、新機能が動作するのを期待しますが、アプリは即座にクラッシュしてしまいます。UIが表示される代わりに、Logcatにはjava.lang.IllegalStateExceptionが表示されます。これは、コード内のデータベース構造が、デバイスに保存されているファイルと一致しなくなったために発生します。

java.lang.IllegalStateException: Roomはデータの整合性を検証できません。スキーマを変更したのにバージョン番号の更新を忘れているようです。

エラーの原因

Roomはローカルデータのゲートキーパー(門番)として機能します。プロジェクトをコンパイルするたびに、Roomは一意の**アイデンティティハッシュ(identity hash)**を生成します。これは、テーブル、カラム、制約を含むスキーマ全体の32文字の指紋のようなものだと考えてください。Roomはこのハッシュを、SQLiteファイル内のroom_master_tableという隠しテーブルに保存します。

アプリが起動すると、Roomは現在のコードの指紋とディスクに保存されている指紋を比較します。もしそれらが異なり、かつversion番号が同じままであれば、Roomはパニックを起こします。古いデータを新しい構造に移行する方法がわからないため、データの破損を防ぐためにアプリを停止させるのです。

クイックフィックス(プロトタイプ制作時)

まだ開発の初期段階で、心配すべき実際のユーザーデータがない場合は、状態をリセットすることで数秒で修正できます。

  • アプリデータの消去: [設定] > [アプリ] > [アプリ名] > [ストレージ] を開き、「データを消去」をタップします。
  • 再インストール: エミュレータまたは実機からアプリを削除し、Android Studioから再度実行します。

これにより、.dbファイルが完全に削除されます。次回の起動時に、Roomは新しいスキーマを使用してデータベースを新規作成し、更新されたハッシュを保存します。これは開発中には有効ですが、本番環境では決して行わないでください。

本番環境向けの恒久的な修正

アプリが公開され、何千人ものユーザーがいる場合、ユーザーにデータを削除してもらうわけにはいきません。変更を処理するための信頼できる方法が2つあります。

オプション1:破壊的なマイグレーション(データ損失あり)

データベースが単なるキャッシュであり、データを失ってもユーザー体験に支障がない場合にこれを使用します。まず、バージョン番号を1から2(または現在のバージョン)に上げます。

@Database(entities = [User::class], version = 2) 
abstract class AppDatabase : RoomDatabase() { ... }

次に、RoomビルダーでfallbackToDestructiveMigration()を使用するように指定します。

val db = Room.databaseBuilder(
    applicationContext,
    AppDatabase::class.java, "app-db"
)
.fallbackToDestructiveMigration()
.build()

Roomはバージョンの上昇を検知し、古いテーブルを削除して再作成します。クラッシュは発生しませんが、データベースは空の状態で開始されます。

オプション2:手動マイグレーション(データ保持)

これがプロフェッショナルな標準です。既存のテーブルをどのように変更するかをSQLiteに正確に伝えるスクリプトを記述する必要があります。例えば、Userテーブルにageカラムを追加したとします。

ステップ1:@Databaseクラスのバージョンを上げます。

@Database(entities = [User::class], version = 2)
abstract class AppDatabase : RoomDatabase() { ... }

ステップ2:マイグレーションオブジェクトを作成します。このコードは、既存の行に触れることなくテーブル構造を更新するための特定のSQLコマンドを実行します。

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE User ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
    }
}

ステップ3:ビルダーにマイグレーションを登録します。

Room.databaseBuilder(context, AppDatabase::class.java, "app-db")
    .addMigrations(MIGRATION_1_2)
    .build()

検証とデバッグ

マイグレーションが実際に成功したかどうかをどのように確認すればよいでしょうか?Android StudioのDatabase Inspector([View] > [Tool Windows] > [App Inspection])を使用してください。ライブのテーブル構造を確認し、SQLクエリを実行して新しいカラムが存在することを確認できます。

複雑なスキーマを扱っていて、なぜハッシュが一致しないのか理由がわからない場合は、エクスポートされたスキーマファイルを確認してください。データベースのアノテーションでexportSchema = trueを設定します。これにより、バージョンごとのJSONファイルが生成されます。時には、DEFAULT値の違いのような些細な詳細が不一致の原因になることもあります。私はよく、ToolCraftのHash Generatorを使用して、これらのJSONファイルのMD5ハッシュを比較します。これにより、隠れた文字やフォーマットの変更がRoomにスキーマを拒否させているかどうかを特定するのに役立ちます。

プロのヒント:@Entityを変更した瞬間に、必ずバージョン番号を更新するようにしましょう。そうすることで、後で謎のクラッシュを追いかける手間を省くことができます。

Related Error Notes