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 );
}
@OverrideView on GitHub (pinned to fad1729dce)
Solutions
- Rename the duplicate CTE in either the DML statement's with-clause or the absorbed source query
- Build the source select query without its own with-clause if the outer DML already declares the CTE
- 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 reusing a select query as the source of insert-select, strip or rename its with-clause
- Keep one naming authority for CTE labels per statement assembly
- Test DML-with-CTE statements end to end in CI
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
- A CTE with the label {} already exists
- A CTE object with the name {} already exists
- Illegal empty CTE name
- Index {} already exists
- UniqueKey {} already exists
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/6e765d749568d1a9.
Report an issue: GitHub.