{"record":{"id":"98d4e022cc1d4fe3","repo":"hibernate/hibernate-orm","slug":"criteria-has-multiple-query-roots-98d4e0","errorCode":null,"errorMessage":"Criteria has multiple query roots","messagePattern":"Criteria has multiple query roots","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java","lineNumber":1035,"sourceCode":"\t\treturn (SqmPredicate) restriction.toPredicate( root, sqmStatement.nodeBuilder() );\n\t}\n\n\tpublic static void validateCriteriaQuery(SqmQueryPart<?> queryPart) {\n\t\tvalidateCriteriaQueryStructure( queryPart );\n\t\tSqmCriteriaRootValidator.validate( queryPart );\n\t}\n\n\tprivate static void validateCriteriaQueryStructure(SqmQueryPart<?> queryPart) {\n\t\tif ( queryPart instanceof SqmQuerySpec<?> sqmQuerySpec ) {\n\t\t\tfinal var selectClause = sqmQuerySpec.getSelectClause();\n\t\t\tif ( selectClause.getSelections().isEmpty() ) {\n\t\t\t\t// make sure there is at least one root\n\t\t\t\tfinal var sqmRoots = sqmQuerySpec.getFromClause().getRoots();\n\t\t\t\tif ( sqmRoots == null || sqmRoots.isEmpty() ) {\n\t\t\t\t\tthrow new IllegalArgumentException( \"Criteria did not define any query roots\" );\n\t\t\t\t}\n\t\t\t\tif ( sqmRoots.size() != 1 ) {\n\t\t\t\t\tthrow new IllegalArgumentException( \"Criteria has multiple query roots\" );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if ( queryPart instanceof SqmQueryGroup<?> queryGroup ) {\n\t\t\tfor ( var part : queryGroup.getQueryParts() ) {\n\t\t\t\tvalidateCriteriaQueryStructure( part );\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tassert false;\n\t\t}\n\t}\n\n\tpublic static void validateCriteriaTree(SqmDeleteStatement<?> statement) {\n\t\tSqmCriteriaRootValidator.validate( statement );\n\t}\n\n\tpublic static void validateCriteriaTree(SqmUpdateStatement<?> statement) {","sourceCodeStart":1017,"sourceCodeEnd":1053,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java#L1017-L1053","documentation":"Thrown as IllegalArgumentException by SqmUtil.validateCriteriaQueryStructure when a CriteriaQuery part has an empty select clause but more than one root in its from clause. With nothing explicitly selected, Hibernate implicitly selects the single root; two or more roots make the implicit selection ambiguous (which root is the result?), so validation fails and demands an explicit select/multiselect.","triggerScenarios":"cq.from(Person.class) followed by cq.from(Address.class) with no cq.select(...) or cq.multiselect(...); joining two entities as separate roots instead of a join and forgetting to project; dynamic builders that add roots per active filter but only set select() in some branches.","commonSituations":"Translating an HQL 'from A a, B b' style query into criteria without adding the select; refactoring a join into a second root; filter-driven criteria where the select statement is appended at the end and a branch returns early.","solutions":["Set an explicit selection: cq.select(personRoot) or cq.multiselect(personRoot, addressRoot)","Drop the extra root and use a join instead: personRoot.join(\"address\")","Use cq.select(cb.construct(Dto.class, ...)) for DTO projections when multiple roots are needed","Verify every dynamic path that adds roots also adds a matching select"],"exampleFix":"// before\nCriteriaQuery<Person> cq = cb.createQuery(Person.class);\nRoot<Person> p = cq.from(Person.class);\ncq.from(Address.class); // second root, nothing selected\n// after\nCriteriaQuery<Person> cq = cb.createQuery(Person.class);\nRoot<Person> p = cq.from(Person.class);\ncq.select(p);","handlingStrategy":"validation","validationCode":"static <X> CriteriaQuery<X> requireSingleImplicitRoot(CriteriaQuery<X> cq) {\n    if (cq.getSelection() == null && cq.getRoots().size() > 1) {\n        throw new IllegalStateException(\n            \"Multiple roots require an explicit select/multiselect\");\n    }\n    return cq;\n}","typeGuard":null,"tryCatchPattern":"try {\n    return em.createQuery(cq).getResultList();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"multiple query roots\")) {\n        cq.select(cq.getRoots().get(0)); // or multiselect(...) for both roots\n        return em.createQuery(cq).getResultList();\n    }\n    throw e;\n}","preventionTips":["Set select()/multiselect() in the same block that adds a second root","Prefer joins over additional roots for related entities","Unit-test every dynamic branch that adds roots"],"tags":["hibernate","criteria-api","jpa","query-validation"],"backgroundTag":"criteria-missing-query-root","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}