hibernate/hibernate-orm · error · IllegalStateException
Criteria path '" + path.getNavigablePath().getFullPath() + "
Error message
Criteria path '" + path.getNavigablePath().getFullPath() + "' references a root from a different tree (criteria nodes may only be used within the same query or subquery in which they were created)
What it means
SqmCriteriaRootValidator walks every path in a criteria query and checks that its root is one of the roots of the query (or subquery) currently being validated, tracked on a stack. A path whose root belongs to a different CriteriaQuery/Subquery tree throws IllegalStateException: criteria nodes are single-owner objects and may not be shared across queries, unlike HQL string fragments.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaRootValidator.java:301
if ( path instanceof SqmTreatedPath<?, ?> treatedPath ) {
return findRoot( treatedPath.getWrappedPath() );
}
else if ( path instanceof SqmRoot<?> root ) {
return root;
}
else {
final var lhs = path.getLhs();
return lhs == null ? null : findRoot( lhs );
}
}
private void validateRoot(SqmRoot<?> root, SqmPath<?> path) {
for ( var validRoots : validRootStack ) {
if ( validRoots.contains( root ) ) {
return;
}
}
throw new IllegalStateException(
"Criteria path '" + path.getNavigablePath().getFullPath() + "' references a root from a different tree "
+ " (criteria nodes may only be used within the same query or subquery in which they were created)"
);
}
}
View on GitHub (pinned to fad1729dce)
Solutions
- Create fresh roots per query: Root<Order> o = query.from(Order.class) inside each builder, and pass paths/predicates as functions of the root (Function<Root<T>, Predicate>) instead of prebuilt nodes.
- Delete cached/static Root fields; if you need a canonical shape, cache the shape (columns, conditions as lambdas), not the nodes.
- For subqueries, always create them from the owning query (query.subquery(...)) and build paths from the subquery's own from(...).
Example fix
// before
private static final Root<Order> SHARED = SOME_QUERY.from(Order.class); // reused everywhere
Predicate p = cb.equal(SHARED.get("status"), "OPEN");
query2.where(p); // IllegalStateException: root from a different tree
// after
Predicate p = cb.equal(query2.from(Order.class).get("status"), "OPEN");
// or parameterize builders:
// Predicate buildOpen(CriteriaBuilder cb, Root<Order> o) { return cb.equal(o.get("status"), "OPEN"); } Defensive patterns
Strategy: type-guard
Validate before calling
// A path is safe for query Q only if its root is one of Q's roots (or a subquery root of Q)
boolean pathBelongsTo(CriteriaQuery<?> q, Path<?> path) {
Root<?> r = findRoot(path);
return r != null && (q.getRoots().contains(r) || isSubqueryRootOf(q, r));
} Type guard
static Root<?> findRoot(Path<?> p) {
Path<?> cur = p;
while (cur.getParentPath() != null) cur = cur.getParentPath();
return cur instanceof Root<?> r ? r : null;
} Try / catch
try {
query.where(predicate);
} catch (IllegalStateException e) {
if (e.getMessage().contains("different tree")) { /* rebuild predicate against query.from(...) and retry */ }
else throw e;
} Prevention
- Never cache or share Root/Subquery/predicate instances across queries; create them per query.
- Express reusable conditions as Function<Root<T>, Predicate> so each query binds its own root.
- Delete static Root fields in utility classes; they are guaranteed cross-tree leaks.
When it happens
Trigger: Reusing a static or cached Root<Entity> (e.g. a shared COMMON_ROOT) in multiple query.multiselect/where calls; applying a Predicate built against query A's root to query B; passing a Subquery created from one query into another query's where; helper repositories that accept roots created elsewhere.
Common situations: Utility predicate builders that cache Root instances for convenience; refactoring so a shared base-query's root leaks into derived queries; copy-paste between query builders; migrating HQL fragment reuse patterns to criteria where nodes were assumed transferable.
Related errors
- Informix does not support binary literals
- Given subclass table number is outside expected range [%s] a
- Not implemented yet
- Result type of all operands must match
- Subquery parent of all operands must match
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/db1162cbed742e12.
Report an issue: GitHub.