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

  1. 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.
  2. Delete cached/static Root fields; if you need a canonical shape, cache the shape (columns, conditions as lambdas), not the nodes.
  3. 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

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


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