{"record":{"id":"00347dbeaa3409b0","repo":"hibernate/hibernate-orm","slug":"entity-join-did-not-specify-a-join-condition","errorCode":null,"errorMessage":"Entity join did not specify a join condition [\" + sqmJoin + \"] (specify a join condition with 'on' or use 'cross join')","messagePattern":"Entity join did not specify a join condition \\[\" \\+ sqmJoin \\+ \"\\] \\(specify a join condition with 'on' or use 'cross join'\\)","errorType":"exception","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java","lineNumber":3924,"sourceCode":"\t\t\t\tnull,\n\t\t\t\tthis\n\t\t);\n\n\t\tfinal var auxiliaryMapping = entityDescriptor.getAuxiliaryMapping();\n\t\tif ( auxiliaryMapping != null ) {\n\t\t\tauxiliaryMapping.applyPredicate( tableGroupJoin, loadQueryInfluencers );\n\t\t}\n\n\t\tfinal var joinPredicate = sqmJoin.getJoinPredicate();\n\t\tif ( joinPredicate != null ) {\n\t\t\tfinal var oldJoin = currentlyProcessingJoin;\n\t\t\tcurrentlyProcessingJoin = sqmJoin;\n\t\t\ttableGroupJoin.applyPredicate( visitNestedTopLevelPredicate( joinPredicate ) );\n\t\t\tcurrentlyProcessingJoin = oldJoin;\n\t\t}\n\t\telse if ( correspondingSqlJoinType != SqlAstJoinType.CROSS ) {\n\t\t\t// TODO: should probably be a SyntaxException\n\t\t\tthrow new SemanticException( \"Entity join did not specify a join condition [\" + sqmJoin + \"]\"\n\t\t\t\t\t+ \" (specify a join condition with 'on' or use 'cross join')\" );\n\t\t}\n\n\t\tif ( transitive ) {\n\t\t\tconsumeExplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() );\n\t\t}\n\t\treturn tableGroup;\n\t}\n\n\tprivate TableGroup consumeDerivedJoin(SqmDerivedJoin<?> sqmJoin, TableGroup parentTableGroup, boolean transitive) {\n\t\tif ( !sqmJoin.isLateral() ) {\n\t\t\t// Temporarily push an empty FromClauseIndex to disallow access to aliases from the top query\n\t\t\t// Only lateral subqueries are allowed to see the aliases\n\t\t\tfromClauseIndexStack.push( new FromClauseIndex( null ) );\n\t\t}\n\t\tfinal var statement = (SelectStatement) sqmJoin.getQueryPart().accept( this );\n\t\tif ( !sqmJoin.isLateral() ) {\n\t\t\tfromClauseIndexStack.pop();","sourceCodeStart":3906,"sourceCodeEnd":3942,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java#L3906-L3942","documentation":"An explicit entity join (a join targeting an entity type rather than an associated path) reached translation with a null join predicate while the resolved SQL join type is not CROSS. HQL requires such joins to carry an 'on' condition, because unlike association joins there is no implicit foreign-key predicate to fall back on.","triggerScenarios":"HQL 'from Order o join Customer c' - joining an unrelated entity with no on-clause and no cross join keyword; criteria code creating JpaEntityJoin (SqmRoot.join(Class)) and never calling .on(...); switching a join from cross to inner without adding a predicate.","commonSituations":"Writing SQL-style joins between unrelated entities in HQL; migrating native SQL joins to HQL; criteria queries that build joins dynamically and skip the on-clause on some code path; refactoring that deletes the on-predicate but keeps the join.","solutions":["Add an explicit on condition: 'from Order o join Customer c on c.id = o.customerId'","Use 'cross join' when you genuinely want no predicate: 'from Order o cross join Customer c'","If the entities are associated, join through the association instead ('join o.customer c') so the predicate is implicit"],"exampleFix":"// before\nselect o, c from Order o join Customer c\n\n// after\nselect o, c from Order o join Customer c on c.id = o.customerId","handlingStrategy":"validation","validationCode":"// Lint: explicit entity joins must have an on-condition or be cross joins\n// crude HQL check - every 'join EntityName' not preceded by 'cross' must be followed by ... on ...\njava.util.regex.Pattern p = java.util.regex.Pattern.compile(\"(?i)\\\\bjoin\\\\s+(?!cross\\\\b)[A-Z]\\\\w*\\\\s+(?!on\\\\b)\");\nif (p.matcher(hql).find()) {\n    throw new IllegalArgumentException(\"Entity join without on-condition; add 'on ...' or use 'cross join'\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    return session.createQuery(hql).getResultList();\n} catch (org.hibernate.query.SemanticException e) {\n    log.error(\"Join rejected: {}\", hql, e);\n    throw e;\n}","preventionTips":["Prefer association joins (join o.customer) over entity joins whenever a mapping exists","Always pair an entity join with an on-clause in code review checklists","Use 'cross join' explicitly when no predicate is intended so intent is visible"],"tags":["hibernate","hql","entity-join","on-condition","query-translation"],"backgroundTag":"entity-join-missing-on","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}