hibernate/hibernate-orm · error · SemanticException

Cannot compare left expression of type '%s' with right expre

Error message

Cannot compare left expression of type '%s' with right expression of type '%s'

What it means

Thrown as SemanticException by TypecheckUtil.assertComparable when the two sides of a comparison predicate have types that are not comparable to each other (after literal-null is allowed and the enum special case is skipped). Hibernate type-checks HQL predicates during semantic analysis: comparing a String path to an Integer literal, a boolean to a number, or an entity to a scalar each fail here instead of producing broken SQL. Note the enum branch in the same method builds its mismatch message but currently does not throw — the non-enum branch is the one that raises this error.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java:453

		// 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()
					);
				}
			}
			else if ( !areTypesComparable( leftType, rightType, bindingContext ) ) {
				throw new SemanticException(
						String.format(
								"Cannot compare left expression of type '%s' with right expression of type '%s'",
								leftType.getTypeName(),
								rightType.getTypeName()
						)
				);
			}
		}
	}

	/**
	 * @see TypecheckUtil#assertComparable(Expression, Expression, BindingContext)
	 */
	public static void assertAssignable(
			@Nullable String hqlString,
			SqmPath<?> targetPath, SqmTypedNode<?> expression,
			BindingContext bindingContext) {
		// allow assigning literal null to things

View on GitHub (pinned to fad1729dce)

Solutions

  1. Fix the literal/parameter type to match the attribute: p.age = 42, not "42"
  2. Bind parameters with the correct Java type: setParameter("age", 42, Integer.class)
  3. Cast explicitly when you need cross-type comparison: cast(p.age as string) = :v
  4. For enums, compare against the enum literal or a properly typed enum parameter

Example fix

// before
List<Person> r = session.createQuery("from Person p where p.age = :age", Person.class)
        .setParameter("age", "42") // String vs int
        .getResultList();
// after
List<Person> r = session.createQuery("from Person p where p.age = :age", Person.class)
        .setParameter("age", 42, Integer.class)
        .getResultList();
Defensive patterns

Strategy: try-catch

Validate before calling

static Object coerceToAttributeType(Object raw, Class<?> attrType) {
    if (raw == null) return null;
    if (attrType.isInstance(raw)) return raw;
    if (attrType == Integer.class || attrType == int.class) return Integer.valueOf(raw.toString());
    if (attrType == Long.class || attrType == long.class) return Long.valueOf(raw.toString());
    if (attrType == Boolean.class || attrType == boolean.class) return Boolean.valueOf(raw.toString());
    throw new IllegalArgumentException("Cannot coerce " + raw + " to " + attrType.getName());
}
// bind with: query.setParameter(name, coerceToAttributeType(value, attrType), attrType);

Type guard

static boolean typesComparable(Class<?> a, Class<?> b) {
    if (a == null || b == null) return true;
    if (a.isAssignableFrom(b) || b.isAssignableFrom(a)) return true;
    return (a == String.class) == (b == String.class)
        && (Number.class.isAssignableFrom(a)) == (Number.class.isAssignableFrom(b))
        && (a == Boolean.class) == (b == Boolean.class);
}

Try / catch

try {
    return session.createQuery(hql, Person.class).setParameter("age", value, Integer.class).getResultList();
} catch (SemanticException e) {
    if (e.getMessage() != null && e.getMessage().contains("Cannot compare")) {
        throw new IllegalArgumentException("Incomparable types in predicate of: " + hql, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL 'where p.age = "42"' (numeric attribute vs string literal); comparing an enum attribute to a plain string literal of a different type; 'where p.active = 1' when active is boolean on a dialect where no implicit conversion applies; comparing an entity-valued path to a scalar; parameters bound with the wrong JavaType so the inferred types disagree.

Common situations: Query strings assembled from user input where numbers arrive as strings; porting native SQL predicates with implicit casts to HQL; enum attributes compared against their name() strings; dialect differences where an implicit conversion used to work.

Related errors


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