hibernate/hibernate-orm · error · UnsupportedOperationException
Follow-on locking for subqueries is not supported
Error message
Follow-on locking for subqueries is not supported
What it means
When pessimistic lock options apply to a query part, the translator picks a strategy via determineLockingStrategy; FOLLOW_ON means Hibernate issues the SELECT unlocked and then locks rows with follow-up SELECT ... FOR UPDATE statements. Follow-on locking only makes sense for a root query — inside a subquery there is nothing to follow up on — so a FOLLOW_ON strategy on a non-root QuerySpec throws UnsupportedOperationException.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:1867
protected void visitForUpdateClause(QuerySpec querySpec) {
if ( querySpec != lockingTarget ) {
// this check is intended to help with Oracle, though
// really any dialect translator could leverage this
return;
}
if ( lockOptions != null && lockOptions.getLockMode().isPessimistic() ) {
final LockStrategy lockStrategy = determineLockingStrategy( querySpec, lockOptions.getFollowOnStrategy() );
switch ( lockStrategy ) {
case CLAUSE: {
lockingClauseStrategy.render( getSqlAppender() );
break;
}
case FOLLOW_ON: {
if ( querySpec.isRoot() ) {
lockOptions = null;
}
else {
throw new UnsupportedOperationException( "Follow-on locking for subqueries is not supported" );
}
break;
}
case NONE: {
// nothing to do
break;
}
}
}
}
protected LockMode getEffectiveLockMode() {
if ( getLockOptions() == null ) {
return LockMode.NONE;
}
else {
final QueryPart currentQueryPart = getQueryPartStack().getCurrent();
if ( currentQueryPart == null || !currentQueryPart.isRoot() ) {View on GitHub (pinned to fad1729dce)
Solutions
- Do not force follow-on locking: remove LockOptions.setFollowOnLocking(true) / the follow-on query hint for this query.
- Restructure the query so the locked SELECT is the root statement (move the union/subquery out or lock a simpler query).
- Drop the pessimistic lock on the subquery-containing query and lock entities separately with EntityManager.find(..., LockModeType.PESSIMISTIC_WRITE) or a follow-up locking query you control.
- Upgrade hibernate-core — follow-on strategy detection for nested query parts has been refined across 6.x releases.
Example fix
// before — forcing follow-on locking on a query with subqueries
List<Order> l = session.createQuery("select o from Order o where o.total > (select avg(o2.total) from Order o2)", Order.class)
.setLockOptions(new LockOptions(LockMode.PESSIMISTIC_WRITE).setFollowOnLocking(true))
.list();
// after — lock in a second, root-level statement you control
List<Long> ids = session.createQuery("select o.id from Order o where o.total > (select avg(o2.total) from Order o2)", Long.class).list();
List<Order> l = session.byId(Order.class).with(LockOptions.UPGRADE).loadMulti(ids); Defensive patterns
Strategy: fallback
Validate before calling
org.hibernate.dialect.Dialect d = sessionFactory.getJdbcServices().getDialect();
boolean followOnDefault = d.getWriteRowLockStrategy() == org.hibernate.LockMode.PESSIMISTIC_WRITE && !d.supportsOuterJoinForUpdate();
if (followOnDefault && queryContainsSubqueries(hql)) {
lockOptions.setFollowOnLocking(false); // avoid forcing follow-on on subqueries
} Try / catch
try { query.setLockOptions(lockOptions).list(); }
catch (UnsupportedOperationException e) {
if (e.getMessage().equals("Follow-on locking for subqueries is not supported")) {
// re-run unlocked, then lock rows in a dedicated root-level statement
List<Long> ids = queryUnlockedIds();
results = lockByIds(ids);
} else { throw e; }
} Prevention
- Never set followOnLocking=true globally; scope it per query.
- Keep locked queries simple and root-level; lock by ids for complex reads.
- Review global lock settings (hibernate.query.followOnLocking) after dialect upgrades.
When it happens
Trigger: Pessimistic locking (LockMode.PESSIMISTIC_WRITE/FORCE_INCREMENT, or LockOptions with follow-on forced via LockOptions.setFollowOnLocking / a dialect defaulting to follow-on) applied to a query where the locked part is a subquery: pagination-wrapped query parts, set-operation arms, or secondary query specs.
Common situations: Setting hibernate.query.followOnLocking to true globally (e.g., for dialects like SQL Server/Sybase where it is the default) and then locking a query containing subqueries/unions; forcing follow-on locking via LockOptions#setFollowOnLocking(Boolean.TRUE); upgrades where a dialect's default write-row-lock strategy changed.
Related errors
- Locking with GROUP BY is not supported
- Locking with HAVING is not supported
- Locking with DISTINCT is not supported
- Locking with OUTER joins is not supported
- Locking with aggregate functions is not supported
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/e51e990bd2f2c98e.
Report an issue: GitHub.