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

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.

Source

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

				navigablePath,
				pathSource,
				sqmFrom,
				alias,
				joinType,
				sqmFrom.nodeBuilder()
		);
		this.function = function;
		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 SqmFunctionJoin<E> copy(SqmCopyContext context) {
		final var existing = context.getCopy( this );
		if ( existing != null ) {
			return existing;
		}
		final var path = context.registerCopy(
				this,

View on GitHub (pinned to fad1729dce)

Solutions

  1. Use `left join lateral` or `join lateral` (SqmJoinType.LEFT/INNER) — the only legal types for lateral function joins.
  2. For RIGHT semantics, reorder FROM so the referenced entity precedes the function join and keep the function join LEFT/INNER.
  3. For FULL OUTER semantics, emulate with UNION of two directional queries or move the correlation into the WHERE clause without LATERAL.

Example fix

// before - IllegalArgumentException: Lateral joins can only be left or inner
JpaFunctionJoin<Long> g = root.joinLateral( cb.generateSeries( 1, 10 ), SqmJoinType.RIGHT );

// after - LEFT (or INNER) lateral function join
JpaFunctionJoin<Long> g = root.joinLateral( cb.generateSeries( 1, 10 ), SqmJoinType.LEFT );

-- HQL equivalent
-- before: select p, i from Person p right join lateral unnest(p.friendIds) i
-- after:  select p, i from Person p left join lateral unnest(p.friendIds) i
Defensive patterns

Strategy: validation

Validate before calling

// validate the join type before building the lateral function join
import org.hibernate.query.sqm.tree.SqmJoinType;

static SqmJoinType requireLateral(SqmJoinType requested) {
    if ( requested != SqmJoinType.LEFT && requested != SqmJoinType.INNER ) {
        throw new IllegalArgumentException(
            "Lateral function joins support only LEFT/INNER, got " + requested );
    }
    return requested;
}

// then
JpaFunctionJoin<X> fj = root.joinLateral( setReturningFunction, requireLateral( joinType ) );

Type guard

static boolean isLateralLegal(org.hibernate.query.sqm.tree.SqmJoinType t) {
    return t == org.hibernate.query.sqm.tree.SqmJoinType.LEFT
        || t == org.hibernate.query.sqm.tree.SqmJoinType.INNER;
}

Try / catch

try {
    return from.joinLateral( function, sqmJoinType );
} catch ( IllegalArgumentException e ) {
    if ( String.valueOf( e.getMessage() ).startsWith( "Lateral joins" ) ) {
        throw new QueryConstructionException(
            "Only LEFT/INNER lateral function joins are legal; got " + sqmJoinType, e );
    }
    throw e;
}

Prevention

When it happens

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

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

Related errors


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