hibernate/hibernate-orm · error · UnsupportedOperationException
DELETE query cannot be sub-query
Error message
DELETE query cannot be sub-query
What it means
SqmDeleteStatement overrides AbstractQuery.subquery(EntityType) to throw UnsupportedOperationException: in the criteria model a DELETE statement cannot host subqueries. Criteria delete statements only support a where predicate on the target root; subselects must be expressed differently (e.g. HQL mutation query or a two-step select-then-delete).
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/delete/SqmDeleteStatement.java:152
return walker.visitDeleteStatement( this );
}
@Override
public void appendHqlString(StringBuilder hql, SqmRenderContext context) {
appendHqlCteString( hql, context );
hql.append( "delete from " );
final SqmRoot<T> root = getTarget();
hql.append( root.getEntityName() );
hql.append( ' ' ).append( root.resolveAlias( context ) );
SqmFromClause.appendJoins( root, hql, context );
SqmFromClause.appendTreatJoins( root, hql, context );
super.appendHqlString( hql, context );
}
@Nonnull
@Override
public <U> Subquery<U> subquery(@Nonnull EntityType<U> type) {
throw new UnsupportedOperationException( "DELETE query cannot be sub-query" );
}
}
View on GitHub (pinned to fad1729dce)
Solutions
- Use an HQL mutation query with a subselect: em.createMutationQuery("delete from Order o where o.customer.id in (select c.id from Customer c where c.status = :s)").
- Run a select criteria query first to collect the affected IDs, then issue the delete restricted to those IDs.
- Guard generic code with a query-type check (only SqmSelectQuery supports subquery()).
Example fix
// before
CriteriaDelete<Order> delete = cb.createCriteriaDelete( Order.class );
Subquery<Long> sq = delete.subquery( Long.class ); // throws UnsupportedOperationException
// after
em.createMutationQuery(
"delete from Order o where o.customer.id in (select c.id from Customer c where c.status = :st)"
).setParameter( "st", "INACTIVE" ).executeUpdate(); Defensive patterns
Strategy: type-guard
Validate before calling
// guard generic tree-processing before calling subquery():
if ( query instanceof SqmSelectQuery<?> ) {
Subquery<U> sq = ( (AbstractQuery<?>) query ).subquery( type );
} else {
// DML statement: use HQL mutation query or two-step select+delete instead
} Type guard
static boolean canHostSubquery(jakarta.persistence.criteria.AbstractQuery<?> q) {
return q instanceof SqmSelectQuery; // delete (and other DML) statements reject subquery()
} Try / catch
try {
sub = deleteStatement.subquery( type );
} catch (UnsupportedOperationException e) {
// "DELETE query cannot be sub-query" -> fall back to HQL mutation query with subselect
em.createMutationQuery( hqlDeleteWithSubselect ).executeUpdate();
} Prevention
- Never call AbstractQuery.subquery(...) without checking the concrete query kind.
- Prefer HQL mutation queries for 'delete where ... in (select ...)' semantics.
- In reusable predicate builders, accept a Subquery/AbstractQuery parameter supplied by select-query callers instead of creating it from the passed query.
When it happens
Trigger: Subquery<Long> sq = cb.createCriteriaDelete(Order.class).subquery(Customer.class); directly, or generic code that walks any AbstractQuery/AbstractSelectionQuery and uniformly calls subquery(type) on it.
Common situations: Porting 'delete where x in (select ...)' logic written for SQL or for a select criteria query; reusable restriction builders that add exists()/in() predicates via query.subquery(...); framework code that processes criteria trees of any kind.
Related errors
- INSERT cannot be basis for subquery
- INSERT query cannot be sub-query
- Illegal empty CTE name
- Expecting DML target entity type [%s] but got [%s]
- Not correlated
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/33f81e9f96bd5048.
Report an issue: GitHub.