hibernate/hibernate-orm · error · IllegalArgumentException

Criteria did not define any query roots

Error message

Criteria did not define any query roots

What it means

Thrown as IllegalArgumentException by SqmUtil.validateCriteriaQueryStructure when a CriteriaQuery part has an empty select clause and its from-clause has no roots. When nothing is selected, Hibernate implicitly selects the single root — with zero roots there is nothing to query, so criteria validation fails before execution. The check recurses into query groups, so a set-operation member without roots also triggers it.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java:1032

			Restriction<? super X> restriction) {
		//noinspection unchecked
		final var root = (JpaRoot<X>) sqmStatement.getRoot( 0, resultType );
		return (SqmPredicate) restriction.toPredicate( root, sqmStatement.nodeBuilder() );
	}

	public static void validateCriteriaQuery(SqmQueryPart<?> queryPart) {
		validateCriteriaQueryStructure( queryPart );
		SqmCriteriaRootValidator.validate( queryPart );
	}

	private static void validateCriteriaQueryStructure(SqmQueryPart<?> queryPart) {
		if ( queryPart instanceof SqmQuerySpec<?> sqmQuerySpec ) {
			final var selectClause = sqmQuerySpec.getSelectClause();
			if ( selectClause.getSelections().isEmpty() ) {
				// make sure there is at least one root
				final var sqmRoots = sqmQuerySpec.getFromClause().getRoots();
				if ( sqmRoots == null || sqmRoots.isEmpty() ) {
					throw new IllegalArgumentException( "Criteria did not define any query roots" );
				}
				if ( sqmRoots.size() != 1 ) {
					throw new IllegalArgumentException( "Criteria has multiple query roots" );
				}
			}
		}
		else if ( queryPart instanceof SqmQueryGroup<?> queryGroup ) {
			for ( var part : queryGroup.getQueryParts() ) {
				validateCriteriaQueryStructure( part );
			}
		}
		else {
			assert false;
		}
	}

	public static void validateCriteriaTree(SqmDeleteStatement<?> statement) {
		SqmCriteriaRootValidator.validate( statement );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Add at least one root: cq.from(Person.class)
  2. If the criteria is optional, skip query execution entirely when no root/entity was configured
  3. Guard dynamic criteria builders: require the entity class parameter up front and call from(entityClass) unconditionally
  4. For query groups, ensure every member part has its own root

Example fix

// before
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
List<Person> people = em.createQuery(cq).getResultList(); // no from() called
// after
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
cq.from(Person.class);
List<Person> people = em.createQuery(cq).getResultList();
Defensive patterns

Strategy: validation

Validate before calling

static <X> CriteriaQuery<X> requireRoot(CriteriaQuery<X> cq, Class<X> entity) {
    if (cq.getRoots().isEmpty()) {
        cq.from(entity);
    }
    return cq;
}
// em.createQuery(requireRoot(cq, Person.class)).getResultList();

Try / catch

try {
    return em.createQuery(cq).getResultList();
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("query roots")) {
        throw new IllegalStateException("Criteria built without a root: " + cq, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: em.createQuery(cb.createQuery()) or cb.createQuery(Person.class) used without ever calling .from(Person.class); a dynamically assembled CriteriaQuery whose from() call was skipped by a filter/condition bug; a CriteriaDefinition or query group where one branch never got a root.

Common situations: Criteria built from user-supplied filter specs where an empty filter set leads to an empty query; refactoring that moves from() into a conditional block; copy-paste criteria templates missing the from() line; new tests constructing criteria stubs.

Related errors


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