hibernate/hibernate-orm · error · IllegalArgumentException
Illegal empty CTE name
Error message
Illegal empty CTE name
What it means
Hibernate throws this IllegalArgumentException from AbstractSqmDmlStatement.validateCteName when a criteria DML statement (SqmInsertStatement/SqmUpdateStatement/SqmDeleteStatement) registers a named CTE with a null or blank label. It applies to the JpaCteContainer methods with(String, criteria), withRecursiveUnionAll(string, ...) and withRecursiveUnionDistinct(string, ...) on the DML statement. It is a fail-fast query-construction error; nothing is executed against the database.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/AbstractSqmDmlStatement.java:156
public <X> JpaCteCriteria<X> withRecursiveUnionAll(
@Nonnull String name,
@Nonnull AbstractQuery<X> baseCriteria,
@Nonnull Function<JpaCteCriteria<X>, AbstractQuery<X>> recursiveCriteriaProducer) {
return withInternal( validateCteName( name ), baseCriteria, false, recursiveCriteriaProducer );
}
@Nonnull
@Override
public <X> JpaCteCriteria<X> withRecursiveUnionDistinct(
@Nonnull String name,
@Nonnull AbstractQuery<X> baseCriteria,
@Nonnull Function<JpaCteCriteria<X>, AbstractQuery<X>> recursiveCriteriaProducer) {
return withInternal( validateCteName( name ), baseCriteria, true, recursiveCriteriaProducer );
}
private String validateCteName(String name) {
if ( name == null || name.isBlank() ) {
throw new IllegalArgumentException( "Illegal empty CTE name" );
}
if ( !isAlphabetic( name.charAt( 0 ) ) ) {
throw new IllegalArgumentException(
String.format(
"Illegal CTE name [%s]. Names must start with an alphabetic character!",
name
)
);
}
return name;
}
private <X> JpaCteCriteria<X> withInternal(String name, AbstractQuery<X> criteria) {
final var cteStatement = new SqmCteStatement<>(
name,
(SqmSelectQuery<X>) criteria,
this,
nodeBuilder()View on GitHub (pinned to fad1729dce)
Solutions
- Pass a non-empty label that starts with an alphabetic character, e.g. with("ids_to_delete", cteQuery).
- If the name is dynamic, validate it (non-null, non-blank) before calling with().
- If you do not care about the label, use the unnamed overloads with(criteria) / withRecursiveUnionAll(criteria, producer) which auto-generate a unique alias.
- Keep CTE labels in shared constants so they are never built ad hoc.
Example fix
// before delete.with( cteName, idsCte ); // cteName is "" or null -> "Illegal empty CTE name" // after delete.with( "ids_to_delete", idsCte );
Defensive patterns
Strategy: validation
Validate before calling
static String cteName(String raw) {
if ( raw == null || raw.isBlank() ) {
throw new IllegalArgumentException( "CTE label must be non-blank, got: " + raw );
}
return raw;
}
// use: delete.with( cteName( label ), cte ); Prevention
- Keep CTE labels in constants; never build them from unvalidated input.
- Prefer the unnamed with(criteria)/withRecursiveUnionAll(criteria, producer) overloads when the label does not matter.
- Assert label validity once at the boundary where dynamic names enter query-building code.
When it happens
Trigger: Calling criteriaUpdate.with(null, cteQuery), .with(" ", cteQuery) or .withRecursiveUnionAll("", base, producer) on a criteria insert/update/delete statement. Any named with* overload whose name argument is null, empty or whitespace-only hits this branch (name == null || name.isBlank()).
Common situations: CTE label built from user input, a config key or a Map key that turns out empty; refactors that renamed or dropped the label constant; translating an HQL 'WITH x AS (...)' to criteria and forgetting to carry the name over.
Related errors
- Illegal CTE name [%s]. Names must start with an alphabetic c
- Expecting DML target entity type [%s] but got [%s]
- A CTE with the label {} already exists
- Illegal search order attribute '{}' passed, which is not par
- Null is an illegal value for cycle mark values!
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/c282d620f4998b48.
Report an issue: GitHub.