{"record":{"id":"5d842269144b0098","repo":"hibernate/hibernate-orm","slug":"cannot-compare-tuples-of-different-lengths","errorCode":null,"errorMessage":"Cannot compare tuples of different lengths","messagePattern":"Cannot compare tuples of different lengths","errorType":"exception","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java","lineNumber":427,"sourceCode":"\t\treturn lhsEntity.isSubclassEntityName( rhsEntity.getEntityName() );\n\t}\n\n\tprivate static EntityPersister getEntityDescriptor(BindingContext bindingContext, String name) {\n\t\treturn bindingContext.getMappingMetamodel()\n\t\t\t\t.getEntityDescriptor( bindingContext.getJpaMetamodel().qualifyImportableName( name ) );\n\t}\n\n\t/**\n\t * @see TypecheckUtil#assertAssignable(String, SqmPath, SqmTypedNode, BindingContext)\n\t */\n\tpublic static void assertComparable(Expression<?> x, Expression<?> y, BindingContext bindingContext) {\n\t\tfinal var left = (SqmExpression<?>) x;\n\t\tfinal var right = (SqmExpression<?>) y;\n\t\tfinal Integer leftTupleLength = left.getTupleLength();\n\t\tfinal Integer rightTupleLength = right.getTupleLength();\n\t\tif ( leftTupleLength != null && rightTupleLength != null\n\t\t\t\t&& leftTupleLength.intValue() != rightTupleLength.intValue() ) {\n\t\t\tthrow new SemanticException( \"Cannot compare tuples of different lengths\" );\n\t\t}\n\n\t\t// SqmMemberOfPredicate is the only one allowing multivalued paths, its comparability is now evaluated in areTypesComparable\n\t\t// i.e. without calling this method, so we can check this here for other Predicates that do call this\n\t\tif ( left instanceof SqmPluralValuedSimplePath || right instanceof SqmPluralValuedSimplePath ) {\n\t\t\tthrow new SemanticException( \"Multivalued paths are only allowed for the 'member of' operator\" );\n\t\t}\n\n\t\t// allow comparing literal null to things\n\t\tif ( !( left instanceof SqmLiteralNull ) && !( right instanceof SqmLiteralNull ) ) {\n\t\t\tfinal var leftType = left.getExpressible();\n\t\t\tfinal var rightType = right.getExpressible();\n\t\t\tif ( leftType != null && rightType != null\n\t\t\t\t\t&& left.isEnum() && right.isEnum() ) {\n\t\t\t\t// this is needed by Hibernate Processor due to the weird\n\t\t\t\t// handling of enumerated types in the annotation processor\n\t\t\t\tif ( !Objects.equals( leftType.getTypeName(), rightType.getTypeName() ) ) {\n\t\t\t\t\tString.format(","sourceCodeStart":409,"sourceCodeEnd":445,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java#L409-L445","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nString hql = \"from Order o where (o.billingCity, o.billingZip, o.billingCountry) = (:city, :zip)\";\n// after\nString hql = \"from Order o where (o.billingCity, o.billingZip) = (:city, :zip)\";","handlingStrategy":"try-catch","validationCode":"static void assertSameTupleLength(List<String> leftAttrs, List<String> rightAttrs) {\n    if (leftAttrs.size() != rightAttrs.size()) {\n        throw new IllegalArgumentException(\"Tuple arity mismatch: \" + leftAttrs.size() + \" vs \" + rightAttrs.size());\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    return session.createQuery(hql, Person.class).getResultList();\n} catch (SemanticException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"tuples of different lengths\")) {\n        throw new IllegalArgumentException(\"Tuple comparison arity mismatch in: \" + hql, e);\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["hibernate","hql","tuple-comparison","semantic-analysis"],"backgroundTag":"hql-type-comparison-error","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}