hibernate/hibernate-orm · error · IllegalArgumentException
Illegal CTE name [%s]. Names must start with an alphabetic c
Error message
Illegal CTE name [%s]. Names must start with an alphabetic character!
What it means
AbstractSqmSelectQuery.validateCteName throws IllegalArgumentException when the CTE name's first character is not alphabetic. SQL identifiers for common table expressions must start with a letter in Hibernate's criteria validation (digits, underscores, or symbols at position 0 are rejected), so names like "1cte", "_tmp", "@x", or names accidentally prefixed by a template marker fail here. The validation runs for every with/withRecursive* variant.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/select/AbstractSqmSelectQuery.java:188
@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()
);
if ( cteStatements.putIfAbsent( name, cteStatement ) != null ) {
throw new IllegalArgumentException( "A CTE with the label " + cteStatement.getCteTable().getCteName() + " already exists" );View on GitHub (pinned to fad1729dce)
Solutions
- Rename the CTE to start with an ASCII letter, e.g. "cte1_results" instead of "1_results"
- Sanitize dynamic names: strip invalid prefixes and prepend a letter if needed before calling with()
- Keep a naming utility that enforces [A-Za-z][A-Za-z0-9_]* for all query identifiers
- Assert generated names in tests for every report/CTE source
Example fix
// before query.with( "1_results", sub ); // IllegalArgumentException // after query.with( "results_1", sub );
Defensive patterns
Strategy: validation
Validate before calling
if (name == null || name.isEmpty() || !Character.isLetter(name.charAt(0))) {
throw new IllegalArgumentException("CTE name must start with a letter: " + name);
}
query.with(name, sub); Prevention
- Enforce [A-Za-z][A-Za-z0-9_]* in your naming helper
- Avoid digit or underscore prefixes when generating names
- Test generated CTE names for every report source
When it happens
Trigger: Criteria `query.with( "1_results", sub )` or `query.with( "_tmp", sub )`; names assembled as prefix+counter where the prefix begins with a digit; sanitizing user input by stripping characters until the name starts with a digit/symbol; HQL-to-criteria translation tools carrying over quoted or decorated identifiers.
Common situations: Auto-generated CTE names ("2024_report", "#1") from reporting layers; i18n or non-ASCII names beginning with a localized character; underscores-first temporary-table naming conventions brought over from SQL dialect habits.
Related errors
- Illegal empty CTE name
- 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/2a07a7c831aea8eb.
Report an issue: GitHub.