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

  1. Keep formulas/computed expressions out of the locking key path - lock on plain columns only
  2. 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
  3. Upgrade Hibernate - later versions broadened the expression types follow-on locking can materialize
  4. 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

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


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