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 thingsView on GitHub (pinned to fad1729dce)
Solutions
- Fix the literal/parameter type to match the attribute: p.age = 42, not "42"
- Bind parameters with the correct Java type: setParameter("age", 42, Integer.class)
- Cast explicitly when you need cross-type comparison: cast(p.age as string) = :v
- 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
- Coerce user input to the attribute's Java type before binding
- Always use the typed setParameter overload
- Compare enums with enum literals or typed enum parameters, not strings
- Cover each predicate with a smoke test against the mapped types
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
- Cannot assign expression of type '%s' to target path '%s' of
- Parameter %d of function '%s()' has type '%s', but argument
- Start and stop parameters of function '%s()' must be of the
- Step parameter of function '%s()' is of type '%s', but must
- Step parameter of function '%s()' is of type '%s', but must
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/3b555857494aa528.
Report an issue: GitHub.