hibernate/hibernate-orm · error · IllegalArgumentException

Lateral joins can only be left or inner. Illegal join type:

Error message

Lateral joins can only be left or inner. Illegal join type: " + joinType

What it means

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).

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/from/SqmDerivedJoin.java:88

				navigablePath,
				pathSource,
				sqmRoot,
				alias,
				joinType,
				sqmRoot.nodeBuilder()
		);
		this.subQuery = subQuery;
		this.lateral = lateral;
	}

	private static SqmJoinType validateJoinType(SqmJoinType joinType, boolean lateral) {
		if ( lateral ) {
			switch ( joinType ) {
				case LEFT:
				case INNER:
					break;
				default:
					throw new IllegalArgumentException( "Lateral joins can only be left or inner. Illegal join type: " + joinType );
			}
		}
		return joinType;
	}

	@Override
	public boolean isImplicitlySelectable() {
		return false;
	}

	@Override
	public SqmDerivedJoin<T> copy(SqmCopyContext context) {
		final var existing = context.getCopy( this );
		if ( existing != null ) {
			return existing;
		}
		//noinspection unchecked
		final var path = context.registerCopy(

View on GitHub (pinned to fad1729dce)

Solutions

  1. Switch to `left join lateral` or plain `join lateral` (SqmJoinType.LEFT / INNER) — these are the only legal lateral join types.
  2. For RIGHT semantics, invert the query: put the referenced entity first in FROM and keep the lateral subquery on its right with LEFT/INNER.
  3. For FULL OUTER semantics, emulate with a UNION of two directional queries, or correlate via a non-lateral subquery in the WHERE clause.

Example fix

// before - IllegalArgumentException: Lateral joins can only be left or inner
JpaDerivedJoin<LineItem> items = order.joinLateral( itemsSub, JoinType.RIGHT );

// after - referenced root stays on the left; lateral join uses LEFT (or INNER)
JpaDerivedJoin<LineItem> items = order.joinLateral( itemsSub, JoinType.LEFT );

/* HQL equivalent:
   before: select o.id, i.name from Order o right join lateral (select i from LineItem i where i.order = o) i on true
   after:  select o.id, i.name from Order o left join lateral (select i from LineItem i where i.order = o) i
*/
Defensive patterns

Strategy: validation

Validate before calling

// validate before constructing the lateral join
import org.hibernate.query.common.JoinType;

static JoinType requireLateralJoinType(JoinType requested) {
    if ( requested != JoinType.LEFT && requested != JoinType.INNER ) {
        throw new IllegalArgumentException(
            "Lateral derived joins support only LEFT/INNER, got " + requested
                + "; reorder the FROM clause instead" );
    }
    return requested;
}

// then
JpaDerivedJoin<X> dj = root.joinLateral( sub, requireLateralJoinType( joinType ) );

Type guard

import org.hibernate.query.sqm.tree.SqmJoinType;

static boolean isLateralLegal(SqmJoinType t) {
    return t == SqmJoinType.LEFT || t == SqmJoinType.INNER;
}

Try / catch

try {
    return from.joinLateral( subquery, requestedType );
} catch ( IllegalArgumentException e ) {
    if ( String.valueOf( e.getMessage() ).startsWith( "Lateral joins" ) ) {
        // construction-time query-shape bug: fail with context, do not retry blindly
        throw new QueryConstructionException(
            "Only LEFT/INNER lateral joins are legal; requested " + requestedType, e );
    }
    throw e;
}

Prevention

When it happens

Trigger: 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 ...`.

Common situations: 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.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/6450aadb83f73bc0. Report an issue: GitHub.