hibernate/hibernate-orm · error · IllegalQueryOperationException
Locking with joins is not supported
Error message
Locking with joins is not supported
What it means
Informix does not allow FOR UPDATE on queries that contain joins. InformixSqlAstTranslator.determineLockingStrategy() therefore checks the locking clause strategy for joins: if follow-on locking is DISALLOWED there is no legal way to apply the lock, and it throws IllegalQueryOperationException('Locking with joins is not supported').
Source
Thrown at hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixSqlAstTranslator.java:351
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
if ( hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateMerge( statement );
}
else {
super.visitUpdateStatementOnly( statement );
}
}
@Override
protected LockStrategy determineLockingStrategy(QuerySpec querySpec, Locking.FollowOn followOnStrategy) {
final LockStrategy lockStrategy = super.determineLockingStrategy( querySpec, followOnStrategy );
final LockingClauseStrategy lockingClauseStrategy = getLockingClauseStrategy();
if ( lockingClauseStrategy != null && lockingClauseStrategy.containsJoins() ) {
// Informix does not allow FOR UPDATE when the query also contains joins
if ( followOnStrategy == Locking.FollowOn.DISALLOW ) {
throw new IllegalQueryOperationException( "Locking with joins is not supported" );
}
else if ( followOnStrategy == Locking.FollowOn.IGNORE ) {
return LockStrategy.NONE;
}
else {
return LockStrategy.FOLLOW_ON;
}
}
else {
return lockStrategy;
}
}
}
View on GitHub (pinned to fad1729dce)
Solutions
- Split the query: lock the root entity in a join-free statement, then load associated data separately
- Allow follow-on locking: remove lock-timeout hints that force single-statement locking so Hibernate can lock rows after fetching
- Switch the affected flows to optimistic (@Version) locking, which has no join restriction
Example fix
// before
Query<A> q = session.createQuery(
"select a from Auction a join a.bids b where b.amount > :min", Auction.class);
q.setLockMode(LockModeType.PESSIMISTIC_WRITE); // -> IllegalQueryOperationException
// after - lock in a join-free statement, then load relations
Auction a = session.createQuery("select a from Auction a where a.id = :id", Auction.class)
.setParameter("id", id)
.unwrap(org.hibernate.query.Query.class)
.setLockOptions(new LockOptions(LockMode.PESSIMISTIC_WRITE))
.getSingleResult();
a.getBids().size(); // load collection after the root is locked Defensive patterns
Strategy: validation
Validate before calling
boolean hasJoins = hql.toLowerCase().matches("(?s).*\\bjoin\\b.*");
boolean pessimistic = lockMode != LockMode.NONE && lockMode != LockMode.OPTIMISTIC
&& lockMode != LockMode.OPTIMISTIC_FORCE_INCREMENT;
if ( session.getJdbcServices().getDialect() instanceof InformixDialect && hasJoins && pessimistic ) {
// lock a join-free root query instead, or allow follow-on locking
} Type guard
static boolean isInformix(Dialect d) { return d instanceof InformixDialect; } Try / catch
try {
query.setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList();
} catch (IllegalQueryOperationException e) {
if ( String.valueOf(e.getMessage()).contains("Locking with joins") ) {
// re-issue as: lock root by id in a join-free query, then fetch relations
}
throw e;
} Prevention
- Never combine FOR UPDATE with joins on Informix - lock by primary key in a dedicated join-free query
- Avoid global pessimistic-lock timeout hints; they can disallow the follow-on fallback
- Prefer optimistic locking for flows that must run on multiple databases
When it happens
Trigger: Applying pessimistic locking (LockOptions/LockModeType.PESSIMISTIC_READ or WRITE, or setLockMode on a Query) to an HQL query with a join on Informix, in a context where follow-on locking is disallowed (e.g., lock timeout hints that force single-statement locking). With FollowOn.IGNORE the lock is silently dropped (LockStrategy.NONE); with the default PREFER, follow-on locking is used instead.
Common situations: Detached-object reload patterns ('select a from A a join a.items where ... for update') ported to Informix; batch jobs using pessimistic locks generically over entity graphs; enabling pessimistic locking globally via lock timeout properties.
Related errors
- Connection lock-timeout does not accept skip-locked
- Informix does not support binary literals
- Can't emulate offset clause in subquery
- Summarization is not supported by DBMS!
- Insert conflict 'do update' clause with constraint name is n
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/b733a2186d24521b.
Report an issue: GitHub.