hibernate/hibernate-orm · error · SemanticException
Cannot compare tuples of different lengths
Error message
Cannot compare tuples of different lengths
What it means
Thrown as SemanticException by TypecheckUtil.assertComparable when both sides of a comparison report a tuple length and the lengths differ. Hibernate supports tuple/row-value comparisons in HQL like '(p.firstName, p.lastName) = (:first, :last)'; the semantics of =, <, IN over tuples require equal arity on both sides, so '(a, b) = (x, y, z)' is rejected during semantic analysis before SQL generation.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java:427
return lhsEntity.isSubclassEntityName( rhsEntity.getEntityName() );
}
private static EntityPersister getEntityDescriptor(BindingContext bindingContext, String name) {
return bindingContext.getMappingMetamodel()
.getEntityDescriptor( bindingContext.getJpaMetamodel().qualifyImportableName( name ) );
}
/**
* @see TypecheckUtil#assertAssignable(String, SqmPath, SqmTypedNode, BindingContext)
*/
public static void assertComparable(Expression<?> x, Expression<?> y, BindingContext bindingContext) {
final var left = (SqmExpression<?>) x;
final var right = (SqmExpression<?>) y;
final Integer leftTupleLength = left.getTupleLength();
final Integer rightTupleLength = right.getTupleLength();
if ( leftTupleLength != null && rightTupleLength != null
&& leftTupleLength.intValue() != rightTupleLength.intValue() ) {
throw new SemanticException( "Cannot compare tuples of different lengths" );
}
// SqmMemberOfPredicate is the only one allowing multivalued paths, its comparability is now evaluated in areTypesComparable
// i.e. without calling this method, so we can check this here for other Predicates that do call this
if ( left instanceof SqmPluralValuedSimplePath || right instanceof SqmPluralValuedSimplePath ) {
throw new SemanticException( "Multivalued paths are only allowed for the 'member of' operator" );
}
// allow comparing literal null to things
if ( !( left instanceof SqmLiteralNull ) && !( right instanceof SqmLiteralNull ) ) {
final var leftType = left.getExpressible();
final var rightType = right.getExpressible();
if ( leftType != null && rightType != null
&& left.isEnum() && right.isEnum() ) {
// this is needed by Hibernate Processor due to the weird
// handling of enumerated types in the annotation processor
if ( !Objects.equals( leftType.getTypeName(), rightType.getTypeName() ) ) {
String.format(View on GitHub (pinned to fad1729dce)
Solutions
- Make both tuples the same length: list exactly the attributes of the composite key/id on each side
- For composite keys, compare the embedded path as a whole: 'where p.id = :id' with the embedded object bound
- When building tuples dynamically, assemble left and right sides from the same attribute list
- Prefer IN over = only with matching tuple arity
Example fix
// before String hql = "from Order o where (o.billingCity, o.billingZip, o.billingCountry) = (:city, :zip)"; // after String hql = "from Order o where (o.billingCity, o.billingZip) = (:city, :zip)";
Defensive patterns
Strategy: try-catch
Validate before calling
static void assertSameTupleLength(List<String> leftAttrs, List<String> rightAttrs) {
if (leftAttrs.size() != rightAttrs.size()) {
throw new IllegalArgumentException("Tuple arity mismatch: " + leftAttrs.size() + " vs " + rightAttrs.size());
}
} Try / catch
try {
return session.createQuery(hql, Person.class).getResultList();
} catch (SemanticException e) {
if (e.getMessage() != null && e.getMessage().contains("tuples of different lengths")) {
throw new IllegalArgumentException("Tuple comparison arity mismatch in: " + hql, e);
}
throw e;
} Prevention
- Build both sides of a tuple comparison from one shared attribute list
- Prefer comparing whole embeddable/composite-id paths (p.id = :id) over hand-written tuples
- Review tuple IN lists when the left-hand side changes
When it happens
Trigger: HQL tuple comparisons with different arity: 'where (p.first, p.last) = (:first, :last, :middle)'; comparing an embeddable (treated as a tuple) against a tuple literal of a different number of elements; copying a composite-key equality predicate from another entity whose id has a different number of columns; tuple IN lists whose element arity differs from the left-hand tuple.
Common situations: Composite key / embeddable equality predicates written by hand; queries ported between entities with similar-sounding keys but different column counts; dynamic tuple builders that append filter columns conditionally to only one side.
Related errors
- Numeric literal '${position}' used in 'group by' does not ma
- Multivalued paths are only allowed for the 'member of' opera
- Cannot compare left expression of type '%s' with right expre
- Cannot assign expression of type '%s' to target path '%s' of
- Insert conflict 'do update' clause with constraint name is n
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/5d842269144b0098.
Report an issue: GitHub.