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
- Replace getParent() with Hibernate's getContainingQuery(), which returns the SqmQuery<?> owner regardless of statement type
- Guard the call: only invoke getParent() when subquery.getContainingQuery() instanceof JpaSelectQuery / SqmSelectQuery
- 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
- Prefer getContainingQuery() over getParent() in traversal utilities that may see update/delete statements
- Document which of your helper methods assume select-only subqueries
- Add a test that runs your criteria walker against a CriteriaUpdate with a subquery
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
- DELETE query cannot be sub-query
- Not correlated
- The JPA specification does not support subqueries in the fro
- Correlated derived root does not have an entity type. Use ge
- Correlated derived root does not have an entity type. Use ge
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/f9e81d6e196fa57b.
Report an issue: GitHub.