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

  1. Allow follow-on locking for this query (LockOptions.setFollowOnLocking(true)) — Hibernate then locks rows with follow-up statements.
  2. Change left joins to inner joins where the association is mandatory, removing outer joins from the locking scope.
  3. 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

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


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