hibernate/hibernate-orm · error · IllegalStateException
Could not determine a depth column name after 5 tries!
Error message
Could not determine a depth column name after 5 tries!
What it means
When emulating a BREADTH FIRST search clause (dialect without native SEARCH support), determineDepthColumnName must invent a column name for the emulated depth counter. It tries 'depth', 'depth_1' .. 'depth_4', skipping any name already taken by a CTE column, the search column, the cycle mark column, or the cycle path column. If all five candidates collide it throws IllegalStateException — an artificial-name exhaustion caused by the CTE's own column names.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:2450
final String name = tries == 0 ? baseName : (baseName + "_" + tries);
for ( CteColumn cteColumn : cte.getCteTable().getCteColumns() ) {
if ( name.equals( cteColumn.getColumnExpression() ) ) {
continue OUTER;
}
}
if ( cte.getSearchColumn() != null && name.equals( cte.getSearchColumn().getColumnExpression() ) ) {
continue;
}
if ( cte.getCycleMarkColumn() != null && name.equals( cte.getCycleMarkColumn().getColumnExpression() ) ) {
continue;
}
if ( cte.getCyclePathColumn() != null && name.equals( cte.getCyclePathColumn().getColumnExpression() ) ) {
continue;
}
return name;
}
throw new IllegalStateException( "Could not determine a depth column name after 5 tries!" );
}
protected String determineCyclePathColumnName(CteStatement cte) {
final CteColumn cyclePathColumn = cte.getCyclePathColumn();
if ( cyclePathColumn != null ) {
return cyclePathColumn.getColumnExpression();
}
String baseName = "path";
OUTER: for ( int tries = 0; tries < 5; tries++ ) {
final String name = tries == 0 ? baseName : (baseName + "_" + tries);
for ( CteColumn cteColumn : cte.getCteTable().getCteColumns() ) {
if ( name.equals( cteColumn.getColumnExpression() ) ) {
continue OUTER;
}
}
if ( cte.getSearchColumn() != null
&& name.equals( cte.getSearchColumn().getColumnExpression() ) ) {
continue;View on GitHub (pinned to fad1729dce)
Solutions
- Rename the CTE's data columns so they do not occupy all of depth, depth_1..depth_4 (any single free candidate is enough).
- Give the search column an explicit, non-colliding name via HQL '... set <search_col>' or the criteria API so fewer emulated names are needed.
- Drop the SEARCH clause and compute ordering in the consuming query (order by an explicit depth column you project yourself).
- Fall back to session.createNativeQuery(...) with hand-written SQL if the column names cannot change.
Example fix
-- before: HQL CTE declaring columns that exhaust the emulated depth names with recursive t(id, depth, depth_1, depth_2, depth_3, depth_4) as (...) search breadth first by id set ord select * from t -- after: free up at least one candidate name with recursive t(id, lvl, lvl_1, lvl_2, lvl_3, lvl_4) as (...) search breadth first by id set ord select * from t
Defensive patterns
Strategy: validation
Validate before calling
// Before executing, ensure the emulated depth names are not all taken
java.util.Set<String> reserved = java.util.Set.of("depth", "depth_1", "depth_2", "depth_3", "depth_4");
boolean collision = cteColumnNames.containsAll(reserved); // cteColumnNames = names you project in the CTE
if ( collision && !dialect.supportsRecursiveSearchClause() ) {
throw new IllegalStateException("Rename CTE columns: emulated depth column name cannot be determined");
} Try / catch
try {
return session.createQuery(treeHql, ResultDto.class).getResultList();
} catch (IllegalStateException e) {
if ( e.getMessage() != null && e.getMessage().contains("depth column name") ) {
throw new IllegalStateException("CTE column names depth..depth_4 collide with the emulated search depth counter; rename them", e);
}
throw e;
} Prevention
- Treat 'depth' and 'depth_1'..'depth_4' as reserved names in recursive CTEs that use SEARCH BREADTH FIRST on emulating dialects.
- Prefer explicit, business-prefixed column names (lvl, node_depth) over generic 'depth' in CTE select lists.
- Cover SEARCH-clause CTEs with tests running against the same dialect as production (H2 results hide emulation paths production may take).
When it happens
Trigger: A recursive CTE with a SEARCH BREADTH FIRST clause (HQL 'search breadth first by ... set seq' or criteria setSearchClauseKind(BREADTH_FIRST, ...)) whose CTE table already exposes columns named exactly depth, depth_1, depth_2, depth_3 and depth_4 (or those names collide with the search/cycle columns), on a dialect where dialect.supportsRecursiveSearchClause() is false.
Common situations: Domain models with hierarchical data that already carry a chain of depth-tracking columns (depth, depth_1, ... from a migration or denormalization); refactoring an existing recursive CTE that used those names as regular data columns; search/cycle column explicitly named 'depth_N' colliding with the emulated counter.
Related errors
- Could not determine a path column name after 5 tries!
- Can't emulate search clause for descending search specificat
- Can't emulate search clause for search specifications with e
- Can't emulate order preserving row constructor through strin
- Different CTE with same name [%s] found in different set ope
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/125d03d8a7c6f3de.
Report an issue: GitHub.