hibernate/hibernate-orm · error · SchemaManagementException

Index mismatch - ` ` on table

Error message

Index mismatch - `%s` on table `%s`

What it means

Index validation found an index with the declared name, but its column list disagrees with the mapping: a different column count, or the i-th indexed column's identifier differs from the i-th selectable of the mapped index (the comparison is position-sensitive, in both directions). Hibernate requires the mapped and physical index to cover the same columns in the same order.

Solutions

  1. Recreate the index with exactly the columns and order the mapping declares: DROP INDEX <name>; CREATE INDEX <name> ON <table> (col1, col2);
  2. Or update @Index(columnList=...) to match the real index definition.
  3. If the difference is intentional, drop the mapped declaration or disable index validation (hibernate.tooling.schema.index_validation=NONE).

Example fix

-- before: existing index covers (customer_id) but the mapping declares (customer_id, created_at)
DROP INDEX orders_customer_idx;

-- after: recreate to match the mapping exactly, order included
CREATE INDEX orders_customer_idx ON orders (customer_id, created_at);
Defensive patterns

Strategy: validation

Validate before calling

// Before validate, compare declared column lists (order included) against JDBC index metadata
// expected: "customer_id,created_at" from @Index(columnList)
try (Connection c = dataSource.getConnection();
     ResultSet rs = c.getMetaData().getIndexInfo(null, null, "orders", false, false)) {
    Map<String, List<String>> byIndex = new HashMap<>();
    while (rs.next()) {
        String n = rs.getString("INDEX_NAME");
        if (n != null) byIndex.computeIfAbsent(n, k -> new ArrayList<>()).add(rs.getString("COLUMN_NAME"));
    }
    List<String> actual = byIndex.get("orders_customer_idx");
    if (!List.of("customer_id", "created_at").equals(actual)) {
        throw new IllegalStateException("orders_customer_idx column list/order mismatch: " + actual);
    }
}

Try / catch

try {
    new SchemaValidator().validate(metadata, serviceRegistry);
} catch (SchemaManagementException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Index mismatch")) {
        // recreate the index with exactly the mapped columns/order, or update columnList to reality
    }
    throw e;
}

Prevention

When it happens

Trigger: validate with hibernate.tooling.schema.index_validation=NAMED/ALL where an existing index covers different columns or a different order than @Index(columnList = "a, b") declares: the migration created the index on (b, a); a DBA later replaced a single-column index with a composite one; a column was renamed in the DB but the mapping kept the old name.

Common situations: Hand-tuned production indexes diverging from entity declarations; performance work adding leading columns to indexes without updating mappings; index definitions copied between MySQL and PostgreSQL exports with reordered columns.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/4d6ab80d106e5485. Report an issue: GitHub.

Appendix: source

Thrown at hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java:231

			var indicesMatch = true;
			assert index.getSelectables().size() == index.getColumnSpan();
			if ( index.getColumnSpan() != indexInformation.getIndexedColumns().size() ) {
				indicesMatch = false;
			}
			else {
				for ( int i = 0; i < index.getSelectables().size(); i++ ) {
					final Selectable column = index.getSelectables().get( i );
					final ColumnInformation columnInfo = indexInformation.getIndexedColumns().get( i );
					if ( !column.getText().equals( columnInfo.getColumnIdentifier().getText() ) ) {
						indicesMatch = false;
						break;
					}
				}
			}

			if ( !indicesMatch ) {
				throw new SchemaManagementException(
						String.format(
								ROOT,
								"Index mismatch - `%s` on table `%s`",
								name.render( dialect ),
								tableInformation.getName().render()
						)
				);
			}
		} );
	}

	private void validateUniqueKeys(
			Table table,
			TableInformation tableInformation,
			Metadata metadata,
			ExecutionOptions options,
			Dialect dialect) {
		var validationType = ConstraintValidationType.interpret( UNIQUE_KEY_VALIDATION, options.getConfigurationValues() );

View on GitHub (pinned to fad1729dce)