{"record":{"id":"b733a2186d24521b","repo":"hibernate/hibernate-orm","slug":"locking-with-joins-is-not-supported-b733a2","errorCode":null,"errorMessage":"Locking with joins is not supported","messagePattern":"Locking with joins is not supported","errorType":"exception","errorClass":"IllegalQueryOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixSqlAstTranslator.java","lineNumber":351,"sourceCode":"\n\t@Override\n\tprotected void visitUpdateStatementOnly(UpdateStatement statement) {\n\t\tif ( hasNonTrivialFromClause( statement.getFromClause() ) ) {\n\t\t\tvisitUpdateStatementEmulateMerge( statement );\n\t\t}\n\t\telse {\n\t\t\tsuper.visitUpdateStatementOnly( statement );\n\t\t}\n\t}\n\n\t@Override\n\tprotected LockStrategy determineLockingStrategy(QuerySpec querySpec, Locking.FollowOn followOnStrategy) {\n\t\tfinal LockStrategy lockStrategy = super.determineLockingStrategy( querySpec, followOnStrategy );\n\t\tfinal LockingClauseStrategy lockingClauseStrategy = getLockingClauseStrategy();\n\t\tif ( lockingClauseStrategy != null && lockingClauseStrategy.containsJoins() ) {\n\t\t\t// Informix does not allow FOR UPDATE when the query also contains joins\n\t\t\tif ( followOnStrategy == Locking.FollowOn.DISALLOW ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Locking with joins is not supported\" );\n\t\t\t}\n\t\t\telse if ( followOnStrategy == Locking.FollowOn.IGNORE ) {\n\t\t\t\treturn LockStrategy.NONE;\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn LockStrategy.FOLLOW_ON;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\treturn lockStrategy;\n\t\t}\n\t}\n}\n","sourceCodeStart":333,"sourceCodeEnd":365,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixSqlAstTranslator.java#L333-L365","documentation":"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').","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nQuery<A> q = session.createQuery(\n    \"select a from Auction a join a.bids b where b.amount > :min\", Auction.class);\nq.setLockMode(LockModeType.PESSIMISTIC_WRITE); // -> IllegalQueryOperationException\n\n// after - lock in a join-free statement, then load relations\nAuction a = session.createQuery(\"select a from Auction a where a.id = :id\", Auction.class)\n        .setParameter(\"id\", id)\n        .unwrap(org.hibernate.query.Query.class)\n        .setLockOptions(new LockOptions(LockMode.PESSIMISTIC_WRITE))\n        .getSingleResult();\na.getBids().size(); // load collection after the root is locked","handlingStrategy":"validation","validationCode":"boolean hasJoins = hql.toLowerCase().matches(\"(?s).*\\\\bjoin\\\\b.*\");\nboolean pessimistic = lockMode != LockMode.NONE && lockMode != LockMode.OPTIMISTIC\n        && lockMode != LockMode.OPTIMISTIC_FORCE_INCREMENT;\nif ( session.getJdbcServices().getDialect() instanceof InformixDialect && hasJoins && pessimistic ) {\n    // lock a join-free root query instead, or allow follow-on locking\n}","typeGuard":"static boolean isInformix(Dialect d) { return d instanceof InformixDialect; }","tryCatchPattern":"try {\n    query.setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList();\n} catch (IllegalQueryOperationException e) {\n    if ( String.valueOf(e.getMessage()).contains(\"Locking with joins\") ) {\n        // re-issue as: lock root by id in a join-free query, then fetch relations\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["hibernate","informix","pessimistic-locking","join","for-update"],"backgroundTag":"pessimistic-locking-join-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}