{"record":{"id":"e9278101265485f8","repo":"hibernate/hibernate-orm","slug":"locking-with-aggregate-functions-is-not-supported","errorCode":null,"errorMessage":"Locking with aggregate functions is not supported","messagePattern":"Locking with aggregate functions 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":1976,"sourceCode":"\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\n\t\treturn strategy;\n\t}\n\n\tprotected void visitConflictClause(ConflictClause conflictClause) {\n\t\tif ( conflictClause != null ) {\n\t\t\t// By default, we only support do nothing with an optional constraint name\n\t\t\tif ( !conflictClause.getConstraintColumnNames().isEmpty() ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Insert conflict clause with constraint column names is not supported\" );\n\t\t\t}\n\t\t\tif ( conflictClause.isDoUpdate() ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Insert conflict do update clause is not supported\" );","sourceCodeStart":1958,"sourceCodeEnd":1994,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java#L1958-L1994","documentation":"Last branch of determineLockingStrategy: if the query spec has aggregate functions (count, sum, avg, ...), a native locking clause is impossible, so follow-on locking is required; with FollowOn.DISALLOW the translation throws IllegalQueryOperationException(\"Locking with aggregate functions is not supported\"). Note this triggers on aggregates anywhere in the query spec, even without GROUP BY.","triggerScenarios":"Pessimistic lock mode with follow-on disallowed on a query containing aggregate functions — e.g., 'select count(e) from Employee e' or 'select e, sum(o.total) from ...' with setLockMode(PESSIMISTIC_WRITE).","commonSituations":"Existence/count checks wrapped in locks; DTO projections mixing entities and aggregates under @Lock; generic service layers applying one lock mode to every query including aggregates.","solutions":["Enable follow-on locking on this query via LockOptions.setFollowOnLocking(true).","Remove the lock from aggregate queries — aggregate results are not row locks; lock the contributing rows in a separate FOR UPDATE select if needed.","Split the query: lock ids in a plain select, compute aggregates unlocked."],"exampleFix":"// before — locking an aggregate query\nLong c = em.createQuery(\"select count(e) from Employee e where e.region = :r\", Long.class)\n    .setParameter(\"r\", \"EMEA\")\n    .setLockMode(LockModeType.PESSIMISTIC_WRITE).getSingleResult();\n\n// after — lock rows, not aggregates\nList<Long> ids = em.createQuery(\"select e.id from Employee e where e.region = :r\", Long.class)\n    .setParameter(\"r\", \"EMEA\")\n    .setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList();\nLong c = (long) ids.size();","handlingStrategy":"validation","validationCode":"if (hql.matches(\"(?i).*\\\\b(count|sum|avg|min|max)\\\\s*\\\\(.*\") && lockMode != null && lockMode.isPessimistic()) {\n    throw new IllegalArgumentException(\"Aggregate queries cannot take a native locking clause; lock rows separately\");\n}","typeGuard":null,"tryCatchPattern":"try { em.createQuery(hql).setLockMode(LockModeType.PESSIMISTIC_WRITE).getSingleResult(); }\ncatch (org.hibernate.query.IllegalQueryOperationException e) {\n    if (e.getMessage().equals(\"Locking with aggregate functions is not supported\")) {\n        // rerun without lock, then lock contributing rows by id\n    } else { throw e; }\n}","preventionTips":["Keep aggregate queries unlocked; lock the underlying rows by id when needed.","Do not apply repository-level @Lock defaults to all finder methods."],"tags":["hibernate","orm","pessimistic-locking","aggregate","jpa"],"backgroundTag":"pessimistic-locking-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}