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

  1. Rename the CTE's data columns so they do not occupy all of depth, depth_1..depth_4 (any single free candidate is enough).
  2. Give the search column an explicit, non-colliding name via HQL '... set <search_col>' or the criteria API so fewer emulated names are needed.
  3. Drop the SEARCH clause and compute ordering in the consuming query (order by an explicit depth column you project yourself).
  4. 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

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


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