hibernate/hibernate-orm · error · IllegalArgumentException

A CTE with the label {} already exists

Error message

A CTE with the label {} already exists

What it means

AbstractSqmDmlStatement.putAllCtes merges the CTE statements of another SqmCteContainer into a DML statement's own with-clause, keyed by CTE name. A putIfAbsent hit means the incoming container declares a CTE label the DML statement already has, which throws IllegalArgumentException.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/AbstractSqmDmlStatement.java:75

		super( builder, querySource, parameters );
		this.cteStatements = cteStatements;
		this.target = target;
	}

	protected Map<String, SqmCteStatement<?>> copyCteStatements(SqmCopyContext context) {
		final Map<String, SqmCteStatement<?>> copy =
				new LinkedHashMap<>( cteStatements.size() );
		for ( var entry : cteStatements.entrySet() ) {
			copy.put( entry.getKey(), entry.getValue().copy( context ) );
		}
		return copy;
	}

	protected void putAllCtes(SqmCteContainer cteContainer) {
		for ( var cteStatement : cteContainer.getCteStatements() ) {
			final String cteName = cteStatement.getCteTable().getCteName();
			if ( cteStatements.putIfAbsent( cteName, cteStatement ) != null ) {
				throw new IllegalArgumentException( "A CTE with the label " + cteName + " already exists" );
			}
		}
	}

	public abstract void validate(@Nullable String hql);

	@Override
	@Nonnull
	public Collection<SqmCteStatement<?>> getCteStatements() {
		return cteStatements.values();
	}

	@Override
	@Nullable public SqmCteStatement<?> getCteStatement(String cteLabel) {
		return cteStatements.get( cteLabel );
	}

	@Override

View on GitHub (pinned to fad1729dce)

Solutions

  1. Rename the duplicate CTE in either the DML statement's with-clause or the absorbed source query
  2. Build the source select query without its own with-clause if the outer DML already declares the CTE
  3. When copying DML criteria, clear or namespace CTE names before merging containers

Example fix

// before
insert into T (a) with src as (select ...) select x from src
// where the source select statement also declares src

// after: unique labels
insert into T (a) with src_outer as (select ...) select x from src_outer
Defensive patterns

Strategy: validation

Validate before calling

// Before putAll-style merge: verify no label overlap
Set<String> mine = dmlStatement.getCteStatements().stream()
    .map(s -> s.getCteTable().getCteName()).collect(java.util.stream.Collectors.toSet());
boolean clash = sourceContainer.getCteStatements().stream()
    .map(s -> s.getCteTable().getCteName()).anyMatch(mine::contains);
if (clash) {
    throw new IllegalArgumentException("Refusing to merge CTEs: duplicate label between DML and source query");
}

Try / catch

try {
    session.createQuery(hqlDml).executeUpdate();
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("A CTE with the label")) {
        log.error("Duplicate CTE label in DML: {}", hqlDml);
    }
    throw e;
}

Prevention

When it happens

Trigger: An HQL DML statement with a with-clause whose label also appears in the source/subquery container it absorbs - e.g. 'insert into ... with t as (...) select ... from t' during copy/merge operations; criteria DML (SqmInsertSelectStatement etc.) whose target and source query both define the same CTE name.

Common situations: Building insert-select or update-with-CTE statements programmatically where both halves share boilerplate CTE names; reusing a prepared select query (with its CTEs) as the source of an insert; upgrades that changed how DML statements merge CTE containers.

Related errors


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