hibernate/hibernate-orm · error · IllegalStateException

Cannot call getParent() on update/delete criteria

Error message

Cannot call getParent() on update/delete criteria

What it means

JPA allows subqueries only inside select queries, so SubQuery.getParent() is declared to return the enclosing select AbstractQuery. Hibernate's SQM model additionally permits subqueries in the WHERE clause of UPDATE and DELETE statements, and for those subqueries there is no select parent. getParent() therefore throws IllegalStateException; the owning statement of any kind is available via getContainingQuery().

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/select/SqmSubQuery.java:250

			);
		}
	}

	@Nonnull
	@Override
	public SqmQuery<?> getContainingQuery() {
		return parent;
	}

	@Nonnull
	@Override
	public SqmSelectQuery<?> getParent() {
		// JPA only allows subqueries on select queries
		if ( getContainingQuery() instanceof SqmSelectQuery<?> sqmSelectQuery ) {
			return sqmSelectQuery;
		}
		else {
			throw new IllegalStateException( "Cannot call getParent() on update/delete criteria" );
		}
	}

	@Override
	public @Nullable String getAlias() {
		return alias;
	}

	@Nonnull
	@Override
	public SqmSubQuery<T> alias(@Nonnull String alias) {
		this.alias = alias;
		return this;
	}

	@Nonnull
	@Override
	public SqmSubQuery<T> select(@Nonnull Expression<T> expression) {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Replace getParent() with Hibernate's getContainingQuery(), which returns the SqmQuery<?> owner regardless of statement type
  2. Guard the call: only invoke getParent() when subquery.getContainingQuery() instanceof JpaSelectQuery / SqmSelectQuery
  3. Restructure the predicate so the subquery lives in a select query (e.g., build the values in a select query first)

Example fix

// before
Subquery<Long> sq = update.subquery(Long.class);
...
AbstractQuery<?> parent = sq.getParent(); // IllegalStateException on update/delete

// after
SqmSubQuery<Long> sq = (SqmSubQuery<Long>) update.subquery(Long.class);
...
SqmQuery<?> owner = sq.getContainingQuery(); // works for select, update and delete
Defensive patterns

Strategy: type-guard

Validate before calling

if (sq.getContainingQuery() instanceof JpaSelectQuery<?>) { AbstractQuery<?> p = sq.getParent(); } else { /* update/delete: no select parent */ }

Type guard

static Optional<JpaSelectQuery<?>> selectParentOf(SqmSubQuery<?> sq) {
    return sq.getContainingQuery() instanceof JpaSelectQuery<?> sel
            ? Optional.of(sel)
            : Optional.empty();
}

Try / catch

try { return sq.getParent(); } catch (IllegalStateException e) { if (e.getMessage().contains("update/delete")) return sq.getContainingQuery(); throw e; }

Prevention

When it happens

Trigger: Creating a subquery from a CriteriaUpdate/CriteriaDelete (update.subquery(...)) for a bulk-statement predicate and then calling subquery.getParent() on it — typically from generic tree-walking or query-rewriting code written against the plain JPA API.

Common situations: Shared criteria traversal utilities (query transformers, audit loggers, count-query generators) that call getParent() to walk up the tree and are later applied to mass-update/delete statements with correlated subqueries.

Related errors


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