hibernate/hibernate-orm · error · SemanticException

SQM INSERT-SELECT without bulk insertion capable identifier

Error message

SQM INSERT-SELECT without bulk insertion capable identifier generator: " + identifierGenerator

What it means

For HQL INSERT ... SELECT where the id must be generated by the statement itself, Hibernate can only inline ids if the identifier generator implements BulkInsertionCapableIdentifierGenerator (so it can emit a select fragment like nextval(seq)). When the target entity's generator does not (a plain custom IdentifierGenerator, AUTO resolving to a non-capable generator, etc.), AdditionalInsertValues.applySelections throws SemanticException('SQM INSERT-SELECT without bulk insertion capable identifier generator: <generator>'). Pooled optimizers additionally require window-function support and take the row-numbering path instead.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java:1586

			final var selectClause = querySpec.getSelectClause();
			if ( versionExpression != null ) {
				if ( versionSelection == null ) {
					// The position is irrelevant as this is only needed for insert
					versionSelection = new SqlSelectionImpl( versionExpression );
				}
				selectClause.addSqlSelection( versionSelection );
			}
			if ( discriminatorExpression != null ) {
				if ( discriminatorSelection == null ) {
					// The position is irrelevant as this is only needed for insert
					discriminatorSelection = new SqlSelectionImpl( discriminatorExpression );
				}
				selectClause.addSqlSelection( discriminatorSelection );
			}
			if ( identifierGenerator != null ) {
				if ( identifierSelection == null ) {
					if ( !( identifierGenerator instanceof BulkInsertionCapableIdentifierGenerator bulkInsertionCapableGenerator ) ) {
						throw new SemanticException(
								"SQM INSERT-SELECT without bulk insertion capable identifier generator: " + identifierGenerator );
					}
					if ( identifierGenerator instanceof OptimizableGenerator optimizableGenerator ) {
						final var optimizer = optimizableGenerator.getOptimizer();
						if ( optimizer != null && optimizer.getIncrementSize() > 1
								|| !bulkInsertionCapableGenerator.supportsBulkInsertionIdentifierGeneration() ) {
							// This is a special case where we have a sequence with an optimizer
							// or a table based identifier generator
							if ( !sessionFactory.getJdbcServices().getDialect().supportsWindowFunctions() ) {
								return false;
							}
							else {
								identifierSelection =
										new SqlSelectionImpl( createRowNumberingExpression( querySpec, sessionFactory ) );
								selectClause.addSqlSelection( identifierSelection );
								return true;
							}
						}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Switch the entity id to a bulk-capable strategy: @GeneratedValue(strategy = GenerationType.SEQUENCE) with a plain sequence (no pooled optimizer), or UUID
  2. Make your custom generator implement org.hibernate.id.BulkInsertionCapableIdentifierGenerator (and supportsBulkInsertionIdentifierGeneration() returning true with a valid select fragment)
  3. Supply the id in the statement: include the id column and select a client-side generated value, or omit the entity from insert-select and persist individually
  4. For pooled optimizers on a dialect without window functions, remove the optimizer (allocationSize=1) or move to a database that supports window functions

Example fix

// before: custom generator, not bulk capable
@Id @GeneratedValue(generator = "myGen")
@GenericGenerator(name = "myGen", type = MyLegacyGenerator.class)
private Long id;

// after
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "emp_seq")
@SequenceGenerator(name = "emp_seq", sequenceName = "emp_seq", allocationSize = 1)
private Long id;
Defensive patterns

Strategy: validation

Validate before calling

// Check generator capability before building the insert-select
if (sessionFactory instanceof SessionFactoryImpl sfi) {
    var generator = sfi.getMetamodel().getEntityDescriptor(Employee.class.getName())
            .getGenerator();
    boolean bulkCapable = generator instanceof BulkInsertionCapableIdentifierGenerator b
            && b.supportsBulkInsertionIdentifierGeneration();
    if (!bulkCapable) { /* use sequence strategy or per-row persist */ }
}

Type guard

static boolean bulkInsertionCapable(SessionFactory sf, String entity) {
    var g = sf.getMetamodel().getEntityDescriptor(entity).getGenerator();
    return g instanceof BulkInsertionCapableIdentifierGenerator b
        && b.supportsBulkInsertionIdentifierGeneration();
}

Try / catch

catch (SemanticException e) { if (e.getMessage().contains("bulk insertion capable")) { /* switch id strategy to SEQUENCE/UUID or include ids explicitly */ } else throw e; }

Prevention

When it happens

Trigger: 'insert into Employee (name) select p.name from Person p' where Employee's id uses a custom IdentifierGenerator (or one configured with GenerationType.AUTO that resolves to a non-bulk-capable generator) and the id column is omitted from the target list; assigning generators like ForeignGenerator or hand-rolled generators that never implemented BulkInsertionCapableIdentifierGenerator; insert-select worked for a sequence-based entity but fails for another entity in the same app

Common situations: Legacy Hibernate 5 apps upgrading - generator contracts were reworked (IdentifierGenerator split, BeforeExecutionGenerator/OnExecutionGenerator); custom id generators written pre-Hibernate 6; GenerationType.AUTO/IDENTITY mixes where identity is fine (db-side) but the app also tries insert-select on sequence-less entities; pooled/hi-lo optimizers on databases without window functions.

Related errors


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