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

  1. Pass a non-empty alphanumeric name starting with a letter
  2. Validate/normalize dynamic names before use: null/blank check plus a default fallback label in your builder code
  3. Load CTE names from a checked registry/enum rather than free-form strings
  4. 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

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


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