hibernate/hibernate-orm · error · SemanticException

Tuple constructor must have at least one element

Error message

Tuple constructor must have at least one element

What it means

SqmTuple groups several expressions into one tuple-typed expression (used by in-tuples, multiselect, compound selections). A tuple with zero expressions has no component types to resolve (resolveTupleType) and cannot be rendered, so the constructor throws SemanticException('Tuple constructor must have at least one element').

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/tree/spi/expression/SqmTuple.java:54

		implements JpaCompoundSelection<T> {
	private final List<SqmExpression<?>> groupedExpressions;

	public SqmTuple(NodeBuilder nodeBuilder, SqmExpression<?>... groupedExpressions) {
		this( Arrays.asList( groupedExpressions ), nodeBuilder );
	}

	public SqmTuple(NodeBuilder nodeBuilder, SqmBindableType<T> type, SqmExpression<?>... groupedExpressions) {
		this( Arrays.asList( groupedExpressions ), type, nodeBuilder );
	}

	public SqmTuple(List<SqmExpression<?>> groupedExpressions, NodeBuilder nodeBuilder) {
		this( groupedExpressions, null, nodeBuilder );
	}

	public SqmTuple(List<SqmExpression<?>> groupedExpressions, @Nullable SqmBindableType<T> type, NodeBuilder nodeBuilder) {
		super( type, nodeBuilder );
		if ( groupedExpressions.isEmpty() ) {
			throw new SemanticException( "Tuple constructor must have at least one element" );
		}
		this.groupedExpressions = groupedExpressions;
		if ( type == null ) {
			setExpressibleType( nodeBuilder.getTypeConfiguration().resolveTupleType( groupedExpressions ) );
		}
	}

	@Override
	public SqmTuple<T> copy(SqmCopyContext context) {
		final var existing = context.getCopy( this );
		if ( existing != null ) {
			return existing;
		}
		final List<SqmExpression<?>> groupedExpressions = new ArrayList<>( this.groupedExpressions.size() );
		for ( SqmExpression<?> groupedExpression : this.groupedExpressions ) {
			groupedExpressions.add( groupedExpression.copy( context ) );
		}
		final SqmTuple<T> expression = context.registerCopy(

View on GitHub (pinned to fad1729dce)

Solutions

  1. Guard the empty case: skip the query, return early, or substitute a default selection (e.g. the root id or a literal).
  2. For dynamic IN lists, check emptiness before building the predicate and choose explicit semantics (match-nothing vs. skip filter).
  3. Validate at the API boundary that callers supply at least one selection.

Example fix

// before
List<Selection<?>> sels = buildSelections( filter ); // may be empty
cq.multiselect( sels );                              // SemanticException

// after
if ( sels.isEmpty() ) {
    cq.multiselect( root.get( "id" ) ); // or throw a meaningful domain exception
} else {
    cq.multiselect( sels );
}
Defensive patterns

Strategy: validation

Validate before calling

if ( selections == null || selections.isEmpty() ) {
    throw new IllegalArgumentException( "multiselect/tuple requires at least one selection" );
}
cq.multiselect( selections );

Prevention

When it happens

Trigger: Creating an SqmTuple or builder.tuple with an empty list: cq.multiselect(new ArrayList<>()), builder.tuple(Collections.emptyList()), or building an IN-tuple predicate from a dynamically generated list of value combinations that turned out empty.

Common situations: Dynamic query construction where selections or IN values come from user input, configuration, or a loop that can yield zero items; empty multiselect lists after filtering; criteria helpers that wrap arbitrary lists in tuples.

Related errors


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