hibernate/hibernate-orm · error · IllegalArgumentException

empty selections passed to criteria query typed as Object

Error message

empty selections passed to criteria query typed as Object

What it means

When a criteria (sub)query has result type Object, SqmSubQuery.getResultSelection decides the selection purely from the size of the list passed to multiselect(List): one item becomes that item, several become an array. With zero selections there is nothing to render in the SELECT clause, so Hibernate throws IllegalArgumentException rather than generating invalid SQL. In practice this means multiselect was called with an empty list, or select() was never invoked before execution.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/select/SqmSubQuery.java:295

	public SqmSubQuery<T> multiselect(Selection<?>... selections) {
		validateComplianceMultiselect();
		final Selection<? extends T> resultSelection = getResultSelection( selections );
		getQuerySpec().setSelection( (JpaSelection<T>) resultSelection );
		return this;
	}

	@Override
	public SqmSubQuery<T> multiselect(List<Selection<?>> selectionList) {
		validateComplianceMultiselect();
		getQuerySpec().setSelection( getResultSelection( selectionList ) );
		return this;
	}

	private JpaSelection<T> getResultSelection(List<Selection<?>> selections) {
		final Class<T> resultType = getResultType();
		if ( resultType == Object.class ) {
			final JpaSelection<?> selection = switch ( selections.size() ) {
				case 0 -> throw new IllegalArgumentException(
						"empty selections passed to criteria query typed as Object" );
				case 1 -> (JpaSelection<?>) selections.get( 0 );
				default -> nodeBuilder().array( selections );
			};
			//noinspection unchecked
			return (JpaSelection<T>) selection;
		}
		else if ( Tuple.class.isAssignableFrom( resultType ) ) {
			//noinspection unchecked
			return (JpaSelection<T>) nodeBuilder().tuple( selections );
		}
		else if ( resultType.isArray() ) {
			return nodeBuilder().array( resultType, selections );
		}
		else {
			return nodeBuilder().construct( resultType, selections );
		}
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Ensure the subquery gets exactly one selection before execution: call subquery.select(expression) instead of multiselect when a single value is expected
  2. Guard dynamic builders: if the selection list is empty, throw your own descriptive exception or skip executing the query
  3. Give the subquery a concrete result type (Long, String, Tuple) so mistakes surface earlier

Example fix

// before
List<Selection<?>> empty = List.of();
sub.multiselect(empty); // IllegalArgumentException at getResultSelection

// after
if (selections.isEmpty()) {
    throw new IllegalStateException("Subquery requires at least one selection");
}
sub.multiselect(selections);
Defensive patterns

Strategy: validation

Validate before calling

if (selections == null || selections.isEmpty()) {
    throw new IllegalStateException("Refusing to build subquery with empty selection");
}
sub.multiselect(selections);

Try / catch

try { sub.multiselect(list); } catch (IllegalArgumentException e) { if (e.getMessage().contains("empty selections")) { /* log and skip query or default to select(cb.literal(1)) */ } else throw e; }

Prevention

When it happens

Trigger: query.subquery(Object.class) followed by subquery.multiselect(Collections.emptyList()) or List.of(); dynamic query builders that always call multiselect() and pass a computed list that ends up empty; refactors that drop the select() call.

Common situations: Dynamic-filter APIs where the selection list is assembled from user input or conditionals and can legitimately be empty; generic projection frameworks that wrap every criteria in multiselect without checking.

Related errors


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