{"record":{"id":"b1dcc985179e377a","repo":"hibernate/hibernate-orm","slug":"lateral-joins-can-only-be-left-or-inner-illegal-j-b1dcc9","errorCode":null,"errorMessage":"Lateral joins can only be left or inner. Illegal join type: \" + joinType","messagePattern":"Lateral joins can only be left or inner\\. Illegal join type: \" \\+ joinType","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/from/SqmFunctionJoin.java","lineNumber":87,"sourceCode":"\t\t\t\tnavigablePath,\n\t\t\t\tpathSource,\n\t\t\t\tsqmFrom,\n\t\t\t\talias,\n\t\t\t\tjoinType,\n\t\t\t\tsqmFrom.nodeBuilder()\n\t\t);\n\t\tthis.function = function;\n\t\tthis.lateral = lateral;\n\t}\n\n\tprivate static SqmJoinType validateJoinType(SqmJoinType joinType, boolean lateral) {\n\t\tif ( lateral ) {\n\t\t\tswitch ( joinType ) {\n\t\t\t\tcase LEFT:\n\t\t\t\tcase INNER:\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IllegalArgumentException( \"Lateral joins can only be left or inner. Illegal join type: \" + joinType );\n\t\t\t}\n\t\t}\n\t\treturn joinType;\n\t}\n\n\t@Override\n\tpublic boolean isImplicitlySelectable() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic SqmFunctionJoin<E> copy(SqmCopyContext context) {\n\t\tfinal var existing = context.getCopy( this );\n\t\tif ( existing != null ) {\n\t\t\treturn existing;\n\t\t}\n\t\tfinal var path = context.registerCopy(\n\t\t\t\tthis,","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/from/SqmFunctionJoin.java#L69-L105","documentation":"SqmFunctionJoin models a join to a set-returning function result (HQL `join unnest(:ids) i`, `join generate_series(1,10) g`; criteria `JpaFrom.join(JpaSetReturningFunction, SqmJoinType[, boolean lateral])`). When lateral=true, the function input may reference preceding FROM items, so only LEFT and INNER joins have SQL meaning. The static validateJoinType(SqmJoinType, boolean lateral) helper in this constructor throws IllegalArgumentException for RIGHT/FULL/CROSS as soon as the join node is created — at query-build time, never at execution time.","triggerScenarios":"Criteria `root.joinLateral(setReturningFunction, SqmJoinType.RIGHT)` or `root.join(function, SqmJoinType.FULL, true)`; HQL `right join lateral unnest(:ids) i` or `full join lateral generate_series(1,10) g on ...`.","commonSituations":"Porting native lateral/right-join-function SQL (PostgreSQL lateral unnest) to HQL; dynamic join builders forwarding a user-chosen join type into join(function, type, lateral); assuming LATERAL composes with every join type because the raw SQL engine is permissive.","solutions":["Use `left join lateral` or `join lateral` (SqmJoinType.LEFT/INNER) — the only legal types for lateral function joins.","For RIGHT semantics, reorder FROM so the referenced entity precedes the function join and keep the function join LEFT/INNER.","For FULL OUTER semantics, emulate with UNION of two directional queries or move the correlation into the WHERE clause without LATERAL."],"exampleFix":"// before - IllegalArgumentException: Lateral joins can only be left or inner\nJpaFunctionJoin<Long> g = root.joinLateral( cb.generateSeries( 1, 10 ), SqmJoinType.RIGHT );\n\n// after - LEFT (or INNER) lateral function join\nJpaFunctionJoin<Long> g = root.joinLateral( cb.generateSeries( 1, 10 ), SqmJoinType.LEFT );\n\n-- HQL equivalent\n-- before: select p, i from Person p right join lateral unnest(p.friendIds) i\n-- after:  select p, i from Person p left join lateral unnest(p.friendIds) i","handlingStrategy":"validation","validationCode":"// validate the join type before building the lateral function join\nimport org.hibernate.query.sqm.tree.SqmJoinType;\n\nstatic SqmJoinType requireLateral(SqmJoinType requested) {\n    if ( requested != SqmJoinType.LEFT && requested != SqmJoinType.INNER ) {\n        throw new IllegalArgumentException(\n            \"Lateral function joins support only LEFT/INNER, got \" + requested );\n    }\n    return requested;\n}\n\n// then\nJpaFunctionJoin<X> fj = root.joinLateral( setReturningFunction, requireLateral( joinType ) );","typeGuard":"static boolean isLateralLegal(org.hibernate.query.sqm.tree.SqmJoinType t) {\n    return t == org.hibernate.query.sqm.tree.SqmJoinType.LEFT\n        || t == org.hibernate.query.sqm.tree.SqmJoinType.INNER;\n}","tryCatchPattern":"try {\n    return from.joinLateral( function, sqmJoinType );\n} catch ( IllegalArgumentException e ) {\n    if ( String.valueOf( e.getMessage() ).startsWith( \"Lateral joins\" ) ) {\n        throw new QueryConstructionException(\n            \"Only LEFT/INNER lateral function joins are legal; got \" + sqmJoinType, e );\n    }\n    throw e;\n}","preventionTips":["Only LEFT/INNER for lateral function joins (unnest, generate_series, table functions) — the lateral input depends on preceding FROM items.","Invert direction by reordering FROM: referenced entity first, function join after.","Clamp or validate user-supplied join types in your query API before passing them to joinLateral.","Verify dialect support: lateral function joins may compile to different SQL per database."],"tags":["hibernate","hql","jpa","criteria-api","lateral-join","set-returning-function","join-type"],"backgroundTag":"illegal-lateral-join-type","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}