{"record":{"id":"ddfb172f665c9c4d","repo":"hibernate/hibernate-orm","slug":"locking-with-outer-joins-is-not-supported","errorCode":null,"errorMessage":"Locking with OUTER joins is not supported","messagePattern":"Locking with OUTER joins is not supported","errorType":"exception","errorClass":"IllegalQueryOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java","lineNumber":1965,"sourceCode":"\t\t\tstrategy = LockStrategy.FOLLOW_ON;\n\t\t}\n\n\t\tif ( querySpec.getSelectClause().isDistinct() ) {\n\t\t\tif ( followOnStrategy == Locking.FollowOn.DISALLOW ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Locking with DISTINCT 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\tstrategy = LockStrategy.FOLLOW_ON;\n\t\t}\n\n\t\tif ( !dialect.supportsOuterJoinForUpdate() ) {\n\t\t\tif ( lockingClauseStrategy != null && lockingClauseStrategy.containsOuterJoins() ) {\n\t\t\t\t// we have any outer joins to lock, but the dialect does not support locking outer joins\n\t\t\t\t// \t\t-we need to use follow-on locking if allowed\n\t\t\t\tif ( followOnStrategy == Locking.FollowOn.DISALLOW ) {\n\t\t\t\t\tthrow new IllegalQueryOperationException( \"Locking with OUTER joins is not supported\" );\n\t\t\t\t}\n\t\t\t\telse if ( followOnStrategy == Locking.FollowOn.IGNORE ) {\n\t\t\t\t\treturn LockStrategy.NONE;\n\t\t\t\t}\n\t\t\t\tstrategy = LockStrategy.FOLLOW_ON;\n\t\t\t}\n\t\t}\n\n\t\tif ( hasAggregateFunctions( querySpec ) ) {\n\t\t\tif ( followOnStrategy == Locking.FollowOn.DISALLOW ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Locking with aggregate functions 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\tstrategy = LockStrategy.FOLLOW_ON;\n\t\t}\n","sourceCodeStart":1947,"sourceCodeEnd":1983,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java#L1947-L1983","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before — left join fetch + pessimistic lock on PostgreSQL\nList<Order> os = em.createQuery(\"select o from Order o left join fetch o.invoice\", Order.class)\n    .setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList();\n\n// after — allow follow-on locking (locks rows in a follow-up statement)\nList<Order> os = em.createQuery(\"select o from Order o left join fetch o.invoice\", Order.class)\n    .unwrap(org.hibernate.query.Query.class)\n    .setLockOptions(new org.hibernate.LockOptions(org.hibernate.LockMode.PESSIMISTIC_WRITE).setFollowOnLocking(true))\n    .getResultList();","handlingStrategy":"validation","validationCode":"org.hibernate.dialect.Dialect d = sessionFactory.getJdbcServices().getDialect();\nboolean locksOuterJoins = queryLockClauseHasOuterJoins(hql); // inspect your own query model\nif (!d.supportsOuterJoinForUpdate() && locksOuterJoins && pessimistic) {\n    // either allow follow-on locking or rewrite the outer join as inner\n    lockOptions.setFollowOnLocking(true);\n}","typeGuard":null,"tryCatchPattern":"try { em.createQuery(hql).setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList(); }\ncatch (org.hibernate.query.IllegalQueryOperationException e) {\n    if (e.getMessage().equals(\"Locking with OUTER joins is not supported\")) {\n        em.createQuery(hql).unwrap(org.hibernate.query.Query.class)\n          .setLockOptions(new org.hibernate.LockOptions(org.hibernate.LockMode.PESSIMISTIC_WRITE).setFollowOnLocking(true))\n          .getResultList();\n    } else { throw e; }\n}","preventionTips":["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."],"tags":["hibernate","orm","pessimistic-locking","outer-join","join-fetch","postgresql"],"backgroundTag":"pessimistic-locking-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}