{"record":{"id":"1784092242c969ed","repo":"hibernate/hibernate-orm","slug":"missing-index-named-s-on-table-s","errorCode":null,"errorMessage":"Missing index named `%s` on table `%s`","messagePattern":"Missing index named `(.+?)` on table `(.+?)`","errorType":"exception","errorClass":"SchemaManagementException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java","lineNumber":204,"sourceCode":"\t\t\tassert StringHelper.isNotEmpty( rawName );\n\t\t\tassert Objects.equals( rawName, index.getName() );\n\t\t\tif ( validationType == ConstraintValidationType.NONE ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse if ( validationType == ConstraintValidationType.NAMED ) {\n\t\t\t\tif ( rawName.startsWith( \"IDX\" ) ) {\n\t\t\t\t\t// this is not a great check as the user could very well\n\t\t\t\t\t// have explicitly chosen a name that starts with this as well,\n\t\t\t\t\t// but...\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar name = metadata.getDatabase().toIdentifier( rawName );\n\t\t\tfinal IndexInformation indexInformation = tableInformation.getIndex( name );\n\n\t\t\tif ( indexInformation == null ) {\n\t\t\t\tthrow new SchemaManagementException(\n\t\t\t\t\t\tString.format(\n\t\t\t\t\t\t\t\tROOT,\n\t\t\t\t\t\t\t\t\"Missing index named `%s` on table `%s`\",\n\t\t\t\t\t\t\t\tname.render( dialect ),\n\t\t\t\t\t\t\t\ttableInformation.getName().render()\n\t\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tvar indicesMatch = true;\n\t\t\tassert index.getSelectables().size() == index.getColumnSpan();\n\t\t\tif ( index.getColumnSpan() != indexInformation.getIndexedColumns().size() ) {\n\t\t\t\tindicesMatch = false;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor ( int i = 0; i < index.getSelectables().size(); i++ ) {\n\t\t\t\t\tfinal Selectable column = index.getSelectables().get( i );\n\t\t\t\t\tfinal ColumnInformation columnInfo = indexInformation.getIndexedColumns().get( i );","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/AbstractSchemaValidator.java#L186-L222","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: index declared in the mapping but never created in the database\n@Table(name = \"orders\", indexes = @Index(name = \"orders_customer_idx\", columnList = \"customer_id\"))\n\n// after: ship the matching migration, mapping unchanged\n// CREATE INDEX orders_customer_idx ON orders (customer_id);\n@Table(name = \"orders\", indexes = @Index(name = \"orders_customer_idx\", columnList = \"customer_id\"))","handlingStrategy":"validation","validationCode":"// Before validate with index_validation=NAMED/ALL, confirm declared indexes exist by name\ntry (Connection c = dataSource.getConnection();\n     ResultSet rs = c.getMetaData().getIndexInfo(null, null, \"orders\", false, false)) {\n    Set<String> actual = new HashSet<>();\n    while (rs.next()) {\n        String n = rs.getString(\"INDEX_NAME\");\n        if (n != null) actual.add(n.toLowerCase());\n    }\n    for (String declared : List.of(\"orders_customer_idx\")) {\n        if (!actual.contains(declared.toLowerCase())) {\n            throw new IllegalStateException(\"Index \" + declared + \" missing - run its migration before validate\");\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    new SchemaValidator().validate(metadata, serviceRegistry);\n} catch (SchemaManagementException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Missing index named\")) {\n        // create the index with the declared name or align the @Index name, then retry\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["hibernate","schema-validation","index","hbm2ddl","ddl-migration"],"backgroundTag":"missing-index","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}