hibernate/hibernate-orm · error · IllegalArgumentException

A CTE with the label {} already exists

Error message

A CTE with the label {} already exists

What it means

The CteContainer implementation keys its statements by the CTE table expression (the label) and registers them with putIfAbsent. addCteStatement throws IllegalArgumentException when the key already exists in the same CTE scope - i.e. two CTE statements declare the same label in one statement scope.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java:9496

		@Override
		public Map<String, CteStatement> getCteStatements() {
			return cteStatements;
		}

		@Override
		public CteStatement getCteStatement(String cteLabel) {
			final var cteStatement = cteStatements.get( cteLabel );
			return cteStatement == null && parent != null
					? parent.getCteStatement( cteLabel )
					: cteStatement;
		}

		@Override
		public void addCteStatement(CteStatement cteStatement) {
			final String tableExpression = cteStatement.getCteTable().getTableExpression();
			if ( cteStatements.putIfAbsent( tableExpression, cteStatement ) != null ) {
				throw new IllegalArgumentException( "A CTE with the label " + tableExpression + " already exists" );
			}
		}

		@Override
		public Map<String, CteObject> getCteObjects() {
			return cteObjects;
		}

		@Override
		public CteObject getCteObject(String cteObjectName) {
			final var cteObject = cteObjects.get( cteObjectName );
			return cteObject == null && parent != null
					? parent.getCteObject( cteObjectName )
					: cteObject;
		}

		@Override
		public void addCteObject(CteObject cteObject) {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Rename one of the duplicate CTE labels so each is unique within the statement
  2. Merge the two same-named CTEs if they are logically the same definition
  3. When building CTEs programmatically, derive names from a counter or the content to guarantee uniqueness

Example fix

// before
with ids as (select 1), ids as (select 2) select * from ids

// after
with ids_a as (select 1), ids_b as (select 2) select * from ids_a
Defensive patterns

Strategy: validation

Validate before calling

// Before adding: check the label is free in this scope
if (java.util.Arrays.stream(existingContainer.getCteStatements().toArray())
        .anyMatch(s -> ((SqmCteStatement<?>) s).getCteTable().getCteName().equals(label))) {
    throw new IllegalArgumentException("CTE label '" + label + "' already exists");
}
existingContainer.addCteStatement(newStatement);

Try / catch

try {
    session.createQuery(hql).executeUpdate();
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("already exists")) {
        log.error("Duplicate CTE label in: {}", hql);
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL 'with t as (...), t as (...) select ...'; criteria code calling with(...) twice with the same name on one query; merging subquery CTE containers that both declare the same label into one scope.

Common situations: Copy-paste of with-clause fragments; generated queries concatenating CTE blocks with a fixed label like 'data' or 'ids'; refactor of shared CTE snippets into one statement.

Related errors


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