hibernate/hibernate-orm · error · IllegalArgumentException
Not enough root entities
Error message
Not enough root entities
What it means
AbstractSqmSelectQuery.getRoot(int position, Class type) throws IllegalArgumentException when the requested position is >= the number of roots in the query spec — i.e. the query has fewer FROM entities than the index you asked for. Hibernate's criteria API supports multiple roots (cross join style `from A a, B b`), and getRoot(position, ...) is positional access into that root list; calling it with position 1 when only one root exists, or position 2 when two exist, fails here before the castRoot type check even runs.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/select/AbstractSqmSelectQuery.java:274
return new HashSet<>( getQuerySpec().getRoots() );
}
/**
* @see org.hibernate.query.criteria.JpaCriteriaQuery#getRootList()
*/
@Nonnull
public List<Root<?>> getRootList() {
return new ArrayList<>( getQuerySpec().getRootList() );
}
/**
* @see org.hibernate.query.criteria.JpaCriteriaQuery#getRoot(int, Class)
*/
@Nonnull
public <E> JpaRoot<? extends E> getRoot(int position, Class<E> type) {
final var rootList = getQuerySpec().getRootList();
if ( rootList.size() <= position ) {
throw new IllegalArgumentException( "Not enough root entities" );
}
return castRoot( rootList.get( position ), type );
}
/**
* @see org.hibernate.query.criteria.JpaCriteriaQuery#getRoot(String, Class)
*/
@Nonnull
public <E> JpaRoot<? extends E> getRoot(String alias, Class<E> type) {
for ( var root : getQuerySpec().getRootList() ) {
final String rootAlias = root.getAlias();
if ( rootAlias != null && rootAlias.equals( alias ) ) {
return castRoot( root, type );
}
}
throw new IllegalArgumentException( "No root entity with alias " + alias );
}
View on GitHub (pinned to fad1729dce)
Solutions
- Use `query.getRootList().size()` (or getRoots()) to bound the position before calling getRoot(position, type)
- Prefer alias-based lookup `query.getRoot("b", B.class)` or keep references to the Root objects returned by from()
- When roots are conditional, store the from() results in variables/map instead of relying on positional indexes
- Add roots first, then resolve positions, in that order
Example fix
// before JpaCriteriaQuery<Tuple> q = cb.createTupleQuery(); q.from( Person.class ); JpaRoot<? extends Address> a = q.getRoot( 1, Address.class ); // IllegalArgumentException // after q.from( Address.class ); JpaRoot<? extends Address> a = q.getRoot( 1, Address.class );
Defensive patterns
Strategy: validation
Validate before calling
int roots = query.getRootList().size();
if (position >= roots) {
throw new IllegalArgumentException("Only " + roots + " root(s); position " + position + " invalid");
}
JpaRoot<? extends E> r = query.getRoot(position, type); Prevention
- Hold the Root objects returned by from() instead of re-resolving by position
- Bound positional access with getRootList().size()
- Prefer alias-based getRoot(alias, type) for multi-root queries
When it happens
Trigger: Code attempts to resolve a single root from a select query that has zero roots registered in its query spec.
Common situations: Forgetting to call CriteriaQuery.from() before building selections or predicates that need a root.
Related errors
- Illegal empty CTE name
- Illegal CTE name [%s]. Names must start with an alphabetic c
- empty selections passed to criteria query typed as Object
- No assignments specified as part of UPDATE criteria
- selectQuery has no selection items
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/1bf493c08950cc81.
Report an issue: GitHub.