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

  1. Use membership: 'where :tag member of p.tags' (HQL) or cb.isMember(tag, root.get("tags")) (Criteria)
  2. Compare sizes: 'where size(p.tags) = 2' or cb.size(...)/cb.count in a subquery
  3. Compare a specific element: 'where p.tags[0] = :x' for indexed collections or join the collection and filter the alias
  4. 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

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


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