hibernate/hibernate-orm · error · SchemaManagementException
Missing index named `%s` on table `%s`
Error message
Missing index named `%s` on table `%s`
What it means
Index validation in the schema validator: the mapping declares an index that tableInformation.getIndex(name) cannot find in JDBC metadata. Validation only runs when hibernate.tooling.schema.index_validation is set to NAMED (skips generated names starting with 'IDX') or ALL, so this fires for explicitly named mapped indexes (e.g. @Table(indexes = @Index(name = ...))) that do not exist in the database under that exact identifier.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java:204
assert StringHelper.isNotEmpty( rawName );
assert Objects.equals( rawName, index.getName() );
if ( validationType == ConstraintValidationType.NONE ) {
return;
}
else if ( validationType == ConstraintValidationType.NAMED ) {
if ( rawName.startsWith( "IDX" ) ) {
// this is not a great check as the user could very well
// have explicitly chosen a name that starts with this as well,
// but...
return;
}
}
var name = metadata.getDatabase().toIdentifier( rawName );
final IndexInformation indexInformation = tableInformation.getIndex( name );
if ( indexInformation == null ) {
throw new SchemaManagementException(
String.format(
ROOT,
"Missing index named `%s` on table `%s`",
name.render( dialect ),
tableInformation.getName().render()
)
);
}
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 );View on GitHub (pinned to fad1729dce)
Solutions
- Create the index in the database with exactly the declared name: CREATE INDEX <name> ON <table> (<columns>).
- Or rename the @Index declaration to the name that already exists in the database.
- If indexes are DBA/migration-managed, remove the declaration from the mapping or set hibernate.tooling.schema.index_validation=NONE.
- Keep index DDL in the same migration pipeline as table DDL so mappings and schema never diverge.
Example fix
// before: index declared in the mapping but never created in the database @Table(name = "orders", indexes = @Index(name = "orders_customer_idx", columnList = "customer_id")) // after: ship the matching migration, mapping unchanged // CREATE INDEX orders_customer_idx ON orders (customer_id); @Table(name = "orders", indexes = @Index(name = "orders_customer_idx", columnList = "customer_id"))
Defensive patterns
Strategy: validation
Validate before calling
// Before validate with index_validation=NAMED/ALL, confirm declared indexes exist by name
try (Connection c = dataSource.getConnection();
ResultSet rs = c.getMetaData().getIndexInfo(null, null, "orders", false, false)) {
Set<String> actual = new HashSet<>();
while (rs.next()) {
String n = rs.getString("INDEX_NAME");
if (n != null) actual.add(n.toLowerCase());
}
for (String declared : List.of("orders_customer_idx")) {
if (!actual.contains(declared.toLowerCase())) {
throw new IllegalStateException("Index " + declared + " missing - run its migration before validate");
}
}
} Try / catch
try {
new SchemaValidator().validate(metadata, serviceRegistry);
} catch (SchemaManagementException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Missing index named")) {
// create the index with the declared name or align the @Index name, then retry
}
throw e;
} Prevention
- Every @Index/@Table(indexes=...) declaration ships with a matching CREATE INDEX migration in the same change
- Name indexes explicitly and identically in mapping and migration (avoid generated names)
- Turn index validation on deliberately (hibernate.tooling.schema.index_validation) and keep it on in CI
When it happens
Trigger: hibernate.hbm2ddl.auto=validate combined with hibernate.tooling.schema.index_validation=NAMED or ALL, where the database lacks an index with the declared name: the index declaration was added without shipping a migration; the migration created the index under a different name; identifier case/quoting differs from what the driver reports (PostgreSQL folding).
Common situations: Declarative @Index added to an entity mid-project and validated against legacy schemas; migration tools auto-generating index names different from the mapped ones; enabling index validation for the first time on an existing database; Postgres lowercasing unquoted mixed-case index names.
Related errors
- Index mismatch - `%s` on table `%s`
- Missing unique constraint named `%s` on table `%s`
- Unique-key mismatch - `%s` on table `%s`
- Unrecognized 'hibernate.hbm2ddl.jdbc_metadata_extraction_str
- Attempt to resolve JDBC metadata failed to find columns for
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/1784092242c969ed.
Report an issue: GitHub.