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
- Switch the entity id to a bulk-capable strategy: @GeneratedValue(strategy = GenerationType.SEQUENCE) with a plain sequence (no pooled optimizer), or UUID
- Make your custom generator implement org.hibernate.id.BulkInsertionCapableIdentifierGenerator (and supportsBulkInsertionIdentifierGeneration() returning true with a valid select fragment)
- 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
- 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
- Use SEQUENCE (allocationSize=1) or UUID ids for entities targeted by insert-select
- Implement BulkInsertionCapableIdentifierGenerator in custom generators during Hibernate 6+ upgrades
- Avoid pooled optimizers on databases without window functions when using insert-select
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
- Not expecting multiple table references for an SQM INSERT-SE
- dialect does not support sequences
- Could not fetch the SequenceInformation from the database
- Error performing isolated work
- Schema validation: missing sequence [%s]
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/79920e9e1782d0a8.
Report an issue: GitHub.