hibernate/hibernate-orm · error · SemanticException
Multivalued paths are only allowed for the 'member of' opera
Error message
Multivalued paths are only allowed for the 'member of' operator
What it means
Thrown as SemanticException by TypecheckUtil.assertComparable when either side of a comparison is an SqmPluralValuedSimplePath — a path to a plural attribute (a collection such as @OneToMany/@ManyToMany/@ElementCollection). Collections are multivalued and cannot be compared with =, <, like scalars; only the 'member of' predicate (and the internal comparability check used by it) accepts multivalued paths.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java:433
}
/**
* @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(
"Cannot compare left expression of enumerated type '%s' with right expression of enumerated type '%s'",
leftType.getTypeName(),
rightType.getTypeName()
);
}
}View on GitHub (pinned to fad1729dce)
Solutions
- Use membership: 'where :tag member of p.tags' (HQL) or cb.isMember(tag, root.get("tags")) (Criteria)
- Compare sizes: 'where size(p.tags) = 2' or cb.size(...)/cb.count in a subquery
- Compare a specific element: 'where p.tags[0] = :x' for indexed collections or join the collection and filter the alias
- For 'collection equals set' semantics, compare ids of members via a subquery on elements
Example fix
// before String hql = "from Person p where p.nicknames = 'beagle'"; // after String hql = "from Person p where 'beagle' member of p.nicknames";
Defensive patterns
Strategy: try-catch
Validate before calling
// if the attribute is plural, force member-of semantics instead of equality
static boolean isPlural(ManagedType<?> type, String attr) {
return type.getAttribute(attr).isCollection();
}
// build: cb.isMember(value, root.get(attr)) instead of cb.equal(root.get(attr), value) Type guard
static boolean isSingularAttributePath(jakarta.persistence.metamodel.Attribute<?, ?> attr) {
return !attr.isCollection() && attr.getPersistentAttributeType() != Attribute.PersistentAttributeType.ENTITY;
} Try / catch
try {
return session.createQuery(hql, Person.class).getResultList();
} catch (SemanticException e) {
if (e.getMessage() != null && e.getMessage().contains("Multivalued paths")) {
// rewrite equality on a collection into a member-of predicate
String fixed = hql.replace("p.nicknames = :v", ":v member of p.nicknames");
return session.createQuery(fixed, Person.class).getResultList();
}
throw e;
} Prevention
- Never compare collection paths with =; decide between member of, size(), or joined element filters
- Check attr.isCollection() in generic filter builders before emitting cb.equal
- Model 'collection equals' queries as subqueries on element ids
When it happens
Trigger: HQL 'where p.tags = :tags' or 'where p.tags = someLiteral' where tags is a collection attribute; comparing a @OneToMany/@ElementCollection path with =, <>, <, >; criteria comparisons built on a collection path (cb.equal(root.get("tags"), value)); forgetting that the collection itself is not the same as its elements or its size.
Common situations: Trying to filter 'entities whose collection equals X' by analogy with scalar fields; porting SQL that compares a join result set; new entity mappings adding @ElementCollection and reusing old scalar predicates; criteria predicates generated from generic filter specs that treat every attribute as singular.
Related errors
- Numeric literal '${position}' used in 'group by' does not ma
- Cannot compare tuples of different lengths
- Cannot compare left expression of type '%s' with right expre
- Cannot assign expression of type '%s' to target path '%s' of
- Index access is only supported for basic plural and string t
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/10811fcf062f2923.
Report an issue: GitHub.