microg/GmsCore · error · IllegalStateException
Upgrades not supported
Error message
Upgrades not supported
What it means
SafetyNetDatabase.onUpgrade() deliberately throws IllegalStateException("Upgrades not supported") because the database schema (DB_VERSION = 1) has never had a migration path. If SQLite detects an existing snet.db file with an old version number (or a corrupt version), the framework calls onUpgrade, which immediately fails instead of migrating.
Source
Thrown at play-services-safetynet/core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetDatabase.kt:128
val sqLiteStatement = db.compileStatement("DELETE FROM $TABLE_RECENTS WHERE $FIELD_TIMESTAMP + ? < ?")
sqLiteStatement.bindLong(1, timeout.toLong())
sqLiteStatement.bindLong(2, System.currentTimeMillis())
rows += sqLiteStatement.executeUpdateDelete()
if (rows != 0) Log.d(TAG, "Cleared $rows old request(s)")
}
fun clearAllRequests() {
val db = writableDatabase
db.execSQL("DELETE FROM $TABLE_RECENTS")
}
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_TABLE_RECENTS)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
throw IllegalStateException("Upgrades not supported")
}
companion object {
private val TAG = SafetyNetDatabase::class.java.simpleName
private const val DB_NAME = "snet.db"
private const val DB_VERSION = 1
private const val CREATE_TABLE_RECENTS = "CREATE TABLE recents (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT ," +
"request_type TEXT," +
"package_name TEXT," +
"nonce TEXT," +
"timestamp INTEGER," +
"result_status_code INTEGER DEFAULT NULL," +
"result_status_msg TEXT DEFAULT NULL," +
"result_data TEXT DEFAULT NULL)"
private const val TABLE_RECENTS = "recents"
private const val FIELD_ID = "id"
private const val FIELD_REQUEST_TYPE = "request_type"View on GitHub (pinned to 157c9d86ac)
Solutions
- Delete the old snet.db file (clear app data or remove databases/snet.db) so onCreate() recreates it fresh
- Catch the IllegalStateException and fall back to deleting and reopening the database
- Restore a matching app/microG version so the on-disk DB version matches DB_VERSION
- If maintaining the code, implement a real onUpgrade migration instead of throwing
Example fix
// before
try { db = helper.writableDatabase } // throws on old schema
// after
try {
db = helper.writableDatabase
} catch (e: IllegalStateException) {
context.deleteDatabase("snet.db")
db = helper.writableDatabase // recreated via onCreate
} Defensive patterns
Strategy: try-catch
Validate before calling
val v = SQLiteDatabase.openDatabase(dbPath, null, OPEN_READONLY).version; if (v < CURRENT_DB_VERSION) context.deleteDatabase("snet.db") Try / catch
try { helper.writableDatabase } catch (e: IllegalStateException) { context.deleteDatabase("snet.db"); helper.writableDatabase } Prevention
- Treat snet.db as disposable (recents cache)
- Never downgrade the app across schema bumps
- Catch upgrade failures and recreate the DB
When it happens
Trigger: Opening the SafetyNet recents database when the on-disk snet.db has a version lower than the current DB_VERSION, or a schema/pragma mismatch causing SQLiteOpenHelper to attempt an upgrade path.
Common situations: Downgrading the app or microG after a future schema bump, a corrupt/partially-written database reporting a wrong version, or copying a database file from a different version of the app.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/e983d76454f9899f.
Report an issue: GitHub.