hibernate/hibernate-orm · error · IllegalMutationQueryException
Expecting a restricted mutation query [%s], but found %s
Error message
Expecting a restricted mutation query [%s], but found %s
What it means
Thrown by SqmUtil.verifyIsRestrictedMutation as IllegalMutationQueryException when the SQM statement is not an SqmDeleteOrUpdateStatement. A 'restricted mutation' is an UPDATE or DELETE targeting one entity; the check is stricter than the generic non-select check (verifyIsNonSelectStatement), so anything that is not exactly a delete/update statement — a SELECT, or an INSERT-style statement such as 'insert into ... select' — is rejected. It is Hibernate's guard for execution paths that semantically require an entity-restricted UPDATE/DELETE.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java:203
}
}
public static IllegalQueryOperationException expectingNonSelect(SqmStatement<?> sqm, String hqlString) {
return new IllegalQueryOperationException(
String.format(
Locale.ROOT,
"Expecting a non-SELECT Query [%s], but found %s",
SqmDmlStatement.class.getName(),
sqm.getClass().getName()
),
hqlString,
null
);
}
public static void verifyIsRestrictedMutation(SqmStatement<?> sqm, String hqlString) {
if ( ! isRestrictedMutation( sqm ) ) {
throw new IllegalMutationQueryException(
String.format(
Locale.ROOT,
"Expecting a restricted mutation query [%s], but found %s",
SqmDeleteOrUpdateStatement.class.getName(),
sqm.getClass().getName()
),
hqlString
);
}
}
public static @Nullable String determineAffectedTableName(TableGroup tableGroup, ValuedModelPart mapping) {
return tableGroup.getModelPart() instanceof EntityAssociationMapping associationMapping
&& !associationMapping.containsTableReference( mapping.getContainingTableExpression() )
? associationMapping.getAssociatedEntityMappingType().getMappedTableDetails().getTableName()
: null;
}
View on GitHub (pinned to fad1729dce)
Solutions
- Use UPDATE or DELETE HQL with the mutation API for paths that require a restricted mutation
- Use a SelectionQuery (getResultList/getSingleResult) for SELECT statements
- For HQL INSERT statements, route through the general mutation API that accepts inserts (createMutationQuery(...).executeUpdate()) instead of the restricted path
- If you maintain the HQL externally, validate its first keyword before dispatching to the restricted-mutation code path
Example fix
// before
int n = em.createQuery("select p from Person p", Person.class).executeUpdate();
// after
List<Person> all = em.createQuery("select p from Person p", Person.class).getResultList(); Defensive patterns
Strategy: validation
Validate before calling
static boolean isRestrictedMutationHql(String hql) {
String head = hql.stripLeading().toLowerCase(Locale.ROOT);
return head.startsWith("update") || head.startsWith("delete");
}
if (!isRestrictedMutationHql(hql)) {
throw new IllegalArgumentException("Only UPDATE/DELETE allowed here: " + hql);
} Try / catch
try {
int n = session.createMutationQuery(hql).executeUpdate();
} catch (IllegalMutationQueryException e) {
// statement is not an UPDATE/DELETE: reject before any side effects
throw new IllegalStateException("Restricted-mutation endpoint got a non-UPDATE/DELETE statement", e);
} Prevention
- Validate the statement keyword before calling executeUpdate-style APIs
- Do not reuse one query-template string for SELECT and mutation paths
- Cover insert-select copies with dedicated methods that use the general mutation API
When it happens
Trigger: Calling executeUpdate()/mutation execution on a query whose SQM statement is a select (e.g. em.createQuery("select p from Person p").executeUpdate()); feeding an HQL 'insert into Archive(a) select ...' into an API path that validates with verifyIsRestrictedMutation and therefore accepts only SqmDeleteOrUpdateStatement; reusing a query-template string for both insert-based copies and delete/update maintenance.
Common situations: Shared query templates where a SELECT leaks into a mutation path; copy-refresh logic written with insert-select that is later routed through a delete/update-only API; dynamic query builders that assemble the statement kind from configuration; migrating raw SQL batches to HQL mutation statements.
Related errors
- Query string is not a mutation
- No active transaction for update or delete query
- Expecting a selection query, but found '{}'
- Could not resolve attribute '%s' of '%s' due to the attribut
- Expecting a SELECT Query [%s], but found %s
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/203a0c54194253e0.
Report an issue: GitHub.