SonarSource/sonarqube · critical · java.lang.IllegalStateException

Failed to insert row with value %s in table %s

Error message

Failed to insert row with value %s in table %s

What it means

MigrationHistoryImpl.done records a completed migration by inserting its number into SCHEMA_MIGRATIONS and committing. An SQLException during the insert/commit is wrapped in IllegalStateException "Failed to insert row with value %s in table %s". The migration itself ran, but history could not be persisted, so the migration may be re-applied or the platform halted.

Source

Thrown at server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java:93

      return Optional.empty();
    } catch (SQLException e) {
      throw new IllegalStateException("Failed to read content of table " + SCHEMA_MIGRATIONS_TABLE, e);
    }
  }

  @Override
  public void done(RegisteredMigrationStep dbMigration) {
    long migrationNumber = dbMigration.getMigrationNumber();
    try (Connection connection = database.getDataSource().getConnection();
      PreparedStatement statement = connection.prepareStatement("insert into schema_migrations(version) values (?)")) {

      statement.setString(1, String.valueOf(migrationNumber));
      statement.execute();
      if (!connection.getAutoCommit()) {
        connection.commit();
      }
    } catch (SQLException e) {
      throw new IllegalStateException(String.format("Failed to insert row with value %s in table %s", migrationNumber, SCHEMA_MIGRATIONS_TABLE), e);
    }
  }

  @Override
  public long getInitialDbVersion() {
    return initialDbVersion;
  }

  private static List<Long> selectVersions(Connection connection) throws SQLException {
    try (Statement statement = connection.createStatement();
      ResultSet resultSet = statement.executeQuery("select version from " + SCHEMA_MIGRATIONS_TABLE)) {
      List<Long> res = new ArrayList<>();
      while (resultSet.next()) {
        res.add(resultSet.getLong(1));
      }
      return res.stream()
        .sorted(Comparator.naturalOrder())
        .toList();

View on GitHub (pinned to 184c821202)

Solutions

  1. Check the wrapped SQLException cause to distinguish permissions, missing table, constraint violation, or connection failure.
  2. Grant the migration user INSERT (ideally full DDL/DML ownership) on the schema and SCHEMA_MIGRATIONS.
  3. If the table is missing or corrupted, restore it from backup or recreate the schema for a fresh migration run.
  4. Retry the start after fixing the cause; unrecorded migrations will be re-applied safely if they are idempotent, otherwise restore the DB snapshot taken before migration.

Example fix

// before — read-only user used for migration
sonar.jdbc.username=sonar_readonly

// after — owner account with write/DDL rights
sonar.jdbc.username=sonar_owner
Defensive patterns

Strategy: try-catch

Validate before calling

SELECT has_table_privilege('sonar_user', 'schema_migrations', 'INSERT') AS can_insert;
SELECT NOT pg_is_in_recovery() AS is_writable_primary;

Try / catch

try {
  history.done(migrationStep);
} catch (IllegalStateException e) {
  LOGGER.error("Failed to record migration {}; cause={}", migrationStep.getMigrationNumber(), e.getCause());
  throw e;
}

Prevention

When it happens

Trigger: The INSERT into SCHEMA_MIGRATIONS (or its commit) throws SQLException after a migration step completes — permissions, table missing, constraint conflict, or connection loss during the write.

Common situations: DB user lacks INSERT privileges; SCHEMA_MIGRATIONS dropped or renamed; duplicate migration number from a re-run; connection dropped mid-migration on flaky networks; read-only replicas or restricted accounts used during migration.

Related errors


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/178d8debd3fbbcbe. Report an issue: GitHub.