hibernate/hibernate-orm · error · IllegalArgumentException
Illegal empty CTE name
Error message
Illegal empty CTE name
What it means
AbstractSqmSelectQuery.validateCteName throws IllegalArgumentException when the name passed to a with*(...) CTE builder (with, withRecursiveUnionAll, withRecursiveUnionDistinct) is null or blank. Criteria CTEs become SQL common table expressions whose names are identifiers; an empty label cannot be rendered, and since criteria builds the tree in Java there is no parser to catch it — hence explicit validation at registration time.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/select/AbstractSqmSelectQuery.java:185
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;
}
protected <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 alphanumeric name starting with a letter
- Validate/normalize dynamic names before use: null/blank check plus a default fallback label in your builder code
- Load CTE names from a checked registry/enum rather than free-form strings
- Unit-test the query factory with all name sources it can receive
Example fix
// before
String cteName = System.getenv("CTE_NAME"); // may be unset -> null
query.with( cteName, sub ); // IllegalArgumentException
// after
String cteName = Objects.requireNonNullElse( System.getenv("CTE_NAME"), "results" );
query.with( cteName, sub ); Defensive patterns
Strategy: validation
Validate before calling
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("CTE name must be non-empty");
}
query.with(name, sub); Prevention
- Route all CTE naming through one helper that rejects blank names
- Never feed raw config/env values as CTE names without validation
- Default missing names to a stable constant
When it happens
Trigger: Criteria: `query.with( "", subquery )`, `query.with( null, sub )`, or a name built by string concat/configuration that yields blank (e.g. prefix stripped by a naming strategy leaving ""); withRecursiveUnionAll(name, ...) with whitespace-only name.
Common situations: CTE names derived from dynamic input (report names, i18n keys) that can be empty; optional-CTE patterns where the name comes from a map key that was never set; copy-paste of HQL cte examples into criteria where the name literal was dropped.
Related errors
- Illegal CTE name [%s]. Names must start with an alphabetic c
- Illegal CTE name [%s]. Names must start with an alphabetic c
- A CTE with the label %s already exists
- Illegal empty CTE name
- Illegal search order attribute '{}' passed, which is not par
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/77ce614413ac458c.
Report an issue: GitHub.