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

  1. Use UPDATE or DELETE HQL with the mutation API for paths that require a restricted mutation
  2. Use a SelectionQuery (getResultList/getSingleResult) for SELECT statements
  3. For HQL INSERT statements, route through the general mutation API that accepts inserts (createMutationQuery(...).executeUpdate()) instead of the restricted path
  4. 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

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


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