hibernate/hibernate-orm · error · UnsupportedOperationException
Unsupported Expression type (expected ColumnReference) : %s
Error message
Unsupported Expression type (expected ColumnReference) : %s
What it means
Follow-on locking builds a separate locking SELECT keyed on the row's columns; LockingCreationStates materializes SqlSelections for those key expressions. It only knows how to handle a ColumnReference - any other Expression (formula, SQL fragment, tuple, function result, parameter) throws UnsupportedOperationException('Unsupported Expression type (expected ColumnReference)').
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/LockingCreationStates.java:168
public SqlSelection resolveSqlSelection(
Expression expression,
JavaType<?> javaType,
FetchParent fetchParent,
TypeConfiguration typeConfiguration) {
final SqlSelection sqlSelection = sqlSelectionMap.get( expression );
if ( sqlSelection != null ) {
return sqlSelection;
}
if ( expression instanceof ColumnReference columnReference ) {
final var selection =
new SqlSelectionImpl( columnReference, querySpec.getSelectClause().getSqlSelections().size() );
sqlSelectionMap.put( expression, selection );
querySpec.getSelectClause().addSqlSelection( selection );
return selection;
}
throw new UnsupportedOperationException( "Unsupported Expression type (expected ColumnReference) : " + expression );
}
@Override
public ModelPart resolveModelPart(NavigablePath navigablePath) {
return null;
}
@Override
public ImmutableFetchList visitFetches(FetchParent fetchParent) {
final var fetches = new ImmutableFetchList.Builder( fetchParent.getReferencedMappingContainer() );
final var referencedMappingContainer = fetchParent.getReferencedMappingContainer();
final int size = referencedMappingContainer.getNumberOfFetchables();
for ( int i = 0; i < size; i++ ) {
final Fetchable fetchable = referencedMappingContainer.getFetchable( i );
processFetchable( fetchParent, fetchable, fetches );
}
return fetches.build();
}View on GitHub (pinned to fad1729dce)
Solutions
- Keep formulas/computed expressions out of the locking key path - lock on plain columns only
- Lock via a dedicated query selecting only real columns (e.g. 'select id from T where id = :id for update') then refresh/load the entity
- Upgrade Hibernate - later versions broadened the expression types follow-on locking can materialize
- If the dialect forces follow-on locking, issue a native locking statement for this case
Example fix
// before
List<Order> orders = session.createQuery(
"select o.id, count(oi) from Order o join o.items oi group by o.id", Order.class )
.setLockMode( LockModeType.PESSIMISTIC_WRITE ).getResultList(); // aggregate in lock key
// after
List<Long> ids = session.createQuery(
"select o.id from Order o", Long.class )
.setLockMode( LockModeType.PESSIMISTIC_WRITE ).getResultList();
List<Order> orders = session.byMultipleIds( Order.class ).multiLoad( ids ); Defensive patterns
Strategy: validation
Validate before calling
// lock on plain columns only: run the locking query against ids, then load
List<Long> ids = session.createQuery( "select o.id from Order o where ...", Long.class )
.setLockMode( LockModeType.PESSIMISTIC_WRITE )
.getResultList();
List<Order> locked = session.byMultipleIds( Order.class ).multiLoad( ids ); Try / catch
try {
return query.setLockMode( LockModeType.PESSIMISTIC_WRITE ).getResultList();
} catch ( UnsupportedOperationException e ) {
if ( e.getMessage() != null && e.getMessage().contains( "expected ColumnReference" ) ) {
// fall back: lock ids with a plain-column query, then load entities
} else { throw e; }
} Prevention
- Keep @Formula/@ColumnTransformer attributes out of keys used by follow-on locking
- Prefer locking dedicated id-only queries and then loading entities
- Know your dialect: if it forces follow-on locking, avoid locking joins/computed selects
When it happens
Trigger: Pessimistic locking (query.setLockMode(LockModeType.PESSIMISTIC_WRITE), session.buildLockRequest/lock, find with lock options) that forces follow-on locking, where a lock-key or selected expression is not a plain column: @Formula or @ColumnTransformer attributes involved in key resolution, computed expressions in the locking result, custom key mappings.
Common situations: @Formula/@ColumnTransformer on attributes that feed identity/FK resolution; dialects that force follow-on locking (no FOR UPDATE with joins, e.g. legacy SQL Server/Sybase dialects); locking queries that select aggregates or functions.
Related errors
- Identifier property '" + getPath( holder, data ) + "' cannot
- Expecting column for collection id (idbag), but found formul
- Expecting single column in context of <%s name="%s"/>, but f
- formula attribute may not be specified along with <column/>
- Spanner does not support no wait.
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/b9676d36f1103b2b.
Report an issue: GitHub.