{"record":{"id":"6450aadb83f73bc0","repo":"hibernate/hibernate-orm","slug":"lateral-joins-can-only-be-left-or-inner-illegal-j","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/SqmDerivedJoin.java","lineNumber":88,"sourceCode":"\t\t\t\tnavigablePath,\n\t\t\t\tpathSource,\n\t\t\t\tsqmRoot,\n\t\t\t\talias,\n\t\t\t\tjoinType,\n\t\t\t\tsqmRoot.nodeBuilder()\n\t\t);\n\t\tthis.subQuery = subQuery;\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 SqmDerivedJoin<T> 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\t//noinspection unchecked\n\t\tfinal var path = context.registerCopy(","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/from/SqmDerivedJoin.java#L70-L106","documentation":"SqmDerivedJoin models `join (subquery) alias` (criteria `JpaFrom.join(Subquery)` / `joinLateral(Subquery, JoinType)`). When lateral=true the subquery may reference preceding FROM items, which by SQL semantics requires the referenced side to come first — so only LEFT and INNER joins are meaningful for a lateral join. The private static validateJoinType(SqmJoinType, boolean lateral) helper enforces this in the constructor and throws IllegalArgumentException for RIGHT/FULL/CROSS the instant the join node is created (parse/build time, never execution time).","triggerScenarios":"Building a lateral derived join with an illegal type: criteria `root.joinLateral(subquery, org.hibernate.query.common.JoinType.RIGHT)` or `root.join(subquery, JoinType.FULL, true)`; HQL `right join lateral (select ...) d on ...` or `full join lateral (select ...) d on ...`.","commonSituations":"Porting correlated RIGHT/FULL outer subquery joins from native SQL to HQL/criteria; dynamic query builders that pass a user-supplied join type straight into joinLateral; developers assuming any join type composes with LATERAL because the database accepts it in hand-written SQL.","solutions":["Switch to `left join lateral` or plain `join lateral` (SqmJoinType.LEFT / INNER) — these are the only legal lateral join types.","For RIGHT semantics, invert the query: put the referenced entity first in FROM and keep the lateral subquery on its right with LEFT/INNER.","For FULL OUTER semantics, emulate with a UNION of two directional queries, or correlate via a non-lateral subquery in the WHERE clause."],"exampleFix":"// before - IllegalArgumentException: Lateral joins can only be left or inner\nJpaDerivedJoin<LineItem> items = order.joinLateral( itemsSub, JoinType.RIGHT );\n\n// after - referenced root stays on the left; lateral join uses LEFT (or INNER)\nJpaDerivedJoin<LineItem> items = order.joinLateral( itemsSub, JoinType.LEFT );\n\n/* HQL equivalent:\n   before: select o.id, i.name from Order o right join lateral (select i from LineItem i where i.order = o) i on true\n   after:  select o.id, i.name from Order o left join lateral (select i from LineItem i where i.order = o) i\n*/","handlingStrategy":"validation","validationCode":"// validate before constructing the lateral join\nimport org.hibernate.query.common.JoinType;\n\nstatic JoinType requireLateralJoinType(JoinType requested) {\n    if ( requested != JoinType.LEFT && requested != JoinType.INNER ) {\n        throw new IllegalArgumentException(\n            \"Lateral derived joins support only LEFT/INNER, got \" + requested\n                + \"; reorder the FROM clause instead\" );\n    }\n    return requested;\n}\n\n// then\nJpaDerivedJoin<X> dj = root.joinLateral( sub, requireLateralJoinType( joinType ) );","typeGuard":"import org.hibernate.query.sqm.tree.SqmJoinType;\n\nstatic boolean isLateralLegal(SqmJoinType t) {\n    return t == SqmJoinType.LEFT || t == SqmJoinType.INNER;\n}","tryCatchPattern":"try {\n    return from.joinLateral( subquery, requestedType );\n} catch ( IllegalArgumentException e ) {\n    if ( String.valueOf( e.getMessage() ).startsWith( \"Lateral joins\" ) ) {\n        // construction-time query-shape bug: fail with context, do not retry blindly\n        throw new QueryConstructionException(\n            \"Only LEFT/INNER lateral joins are legal; requested \" + requestedType, e );\n    }\n    throw e;\n}","preventionTips":["Restrict lateral joins to LEFT and INNER — RIGHT/FULL/CROSS lateral has no SQL meaning because the lateral input depends on preceding FROM items.","To invert direction, reorder the FROM clause so the referenced table precedes the lateral subquery.","Validate the join type once in your query-DSL layer before calling joinLateral/join(subquery, type, true).","Smoke-test generated SQL per dialect: lateral support varies and some dialects emulate it."],"tags":["hibernate","hql","jpa","criteria-api","lateral-join","derived-join","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"}