hibernate/hibernate-orm · error · IllegalQueryOperationException
Unsupported tuple assignment in update query with joins.
Error message
Unsupported tuple assignment in update query with joins.
What it means
updateSourceAsSubquery rewrites a bulk UPDATE that references other tables (update with joins) into an inline-view/subquery form — used by visitUpdateStatementEmulateMerge (H2, HANA, HSQLDB, DB2) and visitUpdateStatementEmulateTupleSet (DB2). When an assignment targets multiple columns (columnReferences.size() > 1) but the assigned value is not a SqlTuple, the rewrite cannot split the value per column and throws IllegalQueryOperationException.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:1615
final SelectClause selectClause = inlineView.getSelectClause();
final List<Assignment> assignments = statement.getAssignments();
final List<String> columnNames = new ArrayList<>( assignments.size() );
for ( Assignment assignment : assignments ) {
final List<ColumnReference> columnReferences = assignment.getAssignable().getColumnReferences();
final Expression assignedValue = assignment.getAssignedValue();
if ( columnReferences.size() == 1 ) {
selectClause.addSqlSelection( new SqlSelectionImpl( assignedValue ) );
columnNames.add( "c" + columnNames.size() );
}
else if ( assignedValue instanceof SqlTuple sqlTuple ) {
final List<? extends Expression> expressions = sqlTuple.getExpressions();
for ( int i = 0; i < columnReferences.size(); i++ ) {
selectClause.addSqlSelection( new SqlSelectionImpl( expressions.get( i ) ) );
columnNames.add( "c" + columnNames.size() );
}
}
else {
throw new IllegalQueryOperationException( "Unsupported tuple assignment in update query with joins." );
}
}
if ( !correlated ) {
final TableGroup dmlTargetTableGroup = statement.getFromClause().getRoots().get( 0 );
assert dmlTargetTableGroup.getPrimaryTableReference() == statement.getTargetTable();
final EntityMappingType entityMappingType = dmlTargetTableGroup.getModelPart().asEntityMappingType();
final EntityRowIdMapping rowIdMapping =
entityMappingType == null ? null : entityMappingType.getRowIdMapping();
final String rowIdExpression = dialect.rowId( null );
if ( rowIdMapping != null ) {
selectClause.addSqlSelection( new SqlSelectionImpl(
new ColumnReference( statement.getTargetTable(), rowIdMapping )
) );
columnNames.add( "c" + columnNames.size() );
}
else if ( rowIdExpression == null ) {
final var identifierTableMapping = statement.getMutationTarget().getIdentifierTableMapping();
identifierTableMapping.getKeyDetails().forEachSelectable( 0,View on GitHub (pinned to fad1729dce)
Solutions
- Rewrite the multi-column assignment as separate single-column assignments in the SET clause.
- Remove the join from the bulk update (move the join condition into a subquery WHERE), so no emulation rewrite is needed.
- Use a native UPDATE ... (correlated subquery) statement for the multi-column assignment.
- As a last resort perform the update row-by-row through managed entities.
Example fix
// before — tuple assignment to multiple columns with a join (fails on DB2/H2 emulation)
int n = session.createQuery(
"update Order o set o.billing = (select a from Address a where a.id = o.addressId) " +
"where o.status = 'NEW' and o.customer.id = :cid")
.executeUpdate();
// after — split into single-column assignments
int n = session.createQuery(
"update Order o set o.billing.street = (select a.street from Address a where a.id = o.addressId), " +
"o.billing.zip = (select a.zip from Address a where a.id = o.addressId) " +
"where o.status = 'NEW' and o.customer.id = :cid")
.executeUpdate(); Defensive patterns
Strategy: try-catch
Validate before calling
org.hibernate.dialect.Dialect d = sessionFactory.getJdbcServices().getDialect();
boolean updateJoinEmulated = d instanceof org.hibernate.dialect.DB2Dialect
|| d instanceof org.hibernate.dialect.H2Dialect
|| d instanceof org.hibernate.dialect.HANADialect
|| d instanceof org.hibernate.dialect.HSQLDialect;
if (updateJoinEmulated && assignmentTargetsMultipleColumns(hql)) {
// tuple assignment cannot be emulated: split into single-column assignments up front
hql = splitTupleAssignments(hql);
} Try / catch
try { session.createQuery(updateHql).executeUpdate(); }
catch (org.hibernate.query.IllegalQueryOperationException e) {
if (e.getMessage().equals("Unsupported tuple assignment in update query with joins.")) {
// split multi-column SET into single-column assignments and retry
session.createQuery(splitTupleAssignments(updateHql)).executeUpdate();
} else { throw e; }
} Prevention
- In bulk updates with joins, always assign one column per SET item.
- Prefer moving join conditions into WHERE subqueries so no emulation is needed.
- Cover bulk mutation queries with tests on every supported dialect.
When it happens
Trigger: HQL/JPQL bulk update with joins whose SET assigns several columns at once from a non-tuple source, e.g., 'set (a,b) = (select x,y from ...)' or a multi-column assignable paired with a scalar/subquery expression, executed on H2, HANA, HSQLDB or DB2 where update-with-join must be emulated.
Common situations: Assigning embeddables or composite foreign keys in bulk updates ('set e.address = ...') on dialects without native UPDATE...FROM; criteria bulk update mutation queries; queries that work on MySQL/PostgreSQL failing in tests on H2.
Related errors
- No active transaction for update or delete query
- Increment option specified for update of non-versioned entit
- Dialect does not support constraint name in conflict clause
- dynamic instantiation in a sub-query is unsupported
- Insert conflict 'do update' clause with constraint name is n
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/494d709f96950228.
Report an issue: GitHub.