hibernate/hibernate-orm · error · IllegalQueryOperationException
Locking with OUTER joins is not supported
Error message
Locking with OUTER joins is not supported
What it means
When dialect.supportsOuterJoinForUpdate() is false (e.g., PostgreSQL: 'FOR UPDATE cannot be applied to the nullable side of an outer join') and the locking clause would have to lock outer-joined rows, determineLockingStrategy demotes to follow-on locking. With FollowOn.DISALLOW (JPA pessimistic lock default) it throws IllegalQueryOperationException instead. This is the classic LEFT JOIN FETCH + pessimistic lock failure.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:1965
strategy = LockStrategy.FOLLOW_ON;
}
if ( querySpec.getSelectClause().isDistinct() ) {
if ( followOnStrategy == Locking.FollowOn.DISALLOW ) {
throw new IllegalQueryOperationException( "Locking with DISTINCT is not supported" );
}
else if ( followOnStrategy == Locking.FollowOn.IGNORE ) {
return LockStrategy.NONE;
}
strategy = LockStrategy.FOLLOW_ON;
}
if ( !dialect.supportsOuterJoinForUpdate() ) {
if ( lockingClauseStrategy != null && lockingClauseStrategy.containsOuterJoins() ) {
// we have any outer joins to lock, but the dialect does not support locking outer joins
// -we need to use follow-on locking if allowed
if ( followOnStrategy == Locking.FollowOn.DISALLOW ) {
throw new IllegalQueryOperationException( "Locking with OUTER joins is not supported" );
}
else if ( followOnStrategy == Locking.FollowOn.IGNORE ) {
return LockStrategy.NONE;
}
strategy = LockStrategy.FOLLOW_ON;
}
}
if ( hasAggregateFunctions( querySpec ) ) {
if ( followOnStrategy == Locking.FollowOn.DISALLOW ) {
throw new IllegalQueryOperationException( "Locking with aggregate functions is not supported" );
}
else if ( followOnStrategy == Locking.FollowOn.IGNORE ) {
return LockStrategy.NONE;
}
strategy = LockStrategy.FOLLOW_ON;
}
View on GitHub (pinned to fad1729dce)
Solutions
- Allow follow-on locking for this query (LockOptions.setFollowOnLocking(true)) — Hibernate then locks rows with follow-up statements.
- Change left joins to inner joins where the association is mandatory, removing outer joins from the locking scope.
- Drop join fetch, lock a simple root select, and initialize associations afterwards (or via second query).
Example fix
// before — left join fetch + pessimistic lock on PostgreSQL
List<Order> os = em.createQuery("select o from Order o left join fetch o.invoice", Order.class)
.setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList();
// after — allow follow-on locking (locks rows in a follow-up statement)
List<Order> os = em.createQuery("select o from Order o left join fetch o.invoice", Order.class)
.unwrap(org.hibernate.query.Query.class)
.setLockOptions(new org.hibernate.LockOptions(org.hibernate.LockMode.PESSIMISTIC_WRITE).setFollowOnLocking(true))
.getResultList(); Defensive patterns
Strategy: validation
Validate before calling
org.hibernate.dialect.Dialect d = sessionFactory.getJdbcServices().getDialect();
boolean locksOuterJoins = queryLockClauseHasOuterJoins(hql); // inspect your own query model
if (!d.supportsOuterJoinForUpdate() && locksOuterJoins && pessimistic) {
// either allow follow-on locking or rewrite the outer join as inner
lockOptions.setFollowOnLocking(true);
} Try / catch
try { em.createQuery(hql).setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList(); }
catch (org.hibernate.query.IllegalQueryOperationException e) {
if (e.getMessage().equals("Locking with OUTER joins is not supported")) {
em.createQuery(hql).unwrap(org.hibernate.query.Query.class)
.setLockOptions(new org.hibernate.LockOptions(org.hibernate.LockMode.PESSIMISTIC_WRITE).setFollowOnLocking(true))
.getResultList();
} else { throw e; }
} Prevention
- On PostgreSQL, avoid 'left join fetch' + pessimistic lock; use follow-on locking or separate loading.
- Make optional associations inner joins where business rules allow (@Fetch/@JoinColumn nullable=false).
- Run lock-containing queries in integration tests on every supported database.
When it happens
Trigger: Pessimistic lock with follow-on disallowed on a query whose locking clause strategy contains outer joins — typically 'select e from E e left join fetch e.many' (or left join) with setLockMode(PESSIMISTIC_WRITE) — on PostgreSQL or another dialect where supportsOuterJoinForUpdate() is false.
Common situations: Using join fetch to avoid N+1 while pessimistically locking; @Lock(PESSIMISTIC_WRITE) Spring Data queries with @EntityGraph (which adds left joins for nullable associations); same code working on MySQL but failing on PostgreSQL/SQL Server.
Related errors
- Connection lock-timeout does not accept skip-locked
- Connection lock-timeout does not accept no-wait
- Follow-on locking for subqueries is not supported
- Locking with GROUP BY is not supported
- Locking with HAVING is not supported
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/ddfb172f665c9c4d.
Report an issue: GitHub.