{"record":{"id":"2fe2e37345f0e537","repo":"flowable/flowable-engine","slug":"error-compare-types","errorCode":"error.compare.types","errorMessage":"error.compare.types","messagePattern":"error\\.compare\\.types","errorType":"exception","errorClass":"ELException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/misc/BooleanOperations.java","lineNumber":65,"sourceCode":"\t\tif (SIMPLE_FLOAT_TYPES.contains(t1) || SIMPLE_FLOAT_TYPES.contains(t2)) {\n\t\t\treturn converter.convert(o1, Double.class) < converter.convert(o2, Double.class);\n\t\t}\n\t\tif (BigInteger.class.isAssignableFrom(t1) || BigInteger.class.isAssignableFrom(t2)) {\n\t\t\treturn converter.convert(o1, BigInteger.class).compareTo(converter.convert(o2, BigInteger.class)) < 0;\n\t\t}\n\t\tif (SIMPLE_INTEGER_TYPES.contains(t1) || SIMPLE_INTEGER_TYPES.contains(t2)) {\n\t\t\treturn converter.convert(o1, Long.class) < converter.convert(o2, Long.class);\n\t\t}\n\t\tif (t1 == String.class || t2 == String.class) {\n\t\t\treturn converter.convert(o1, String.class).compareTo(converter.convert(o2, String.class)) < 0;\n\t\t}\n\t\tif (o1 instanceof Comparable) {\n\t\t\treturn ((Comparable)o1).compareTo(o2) < 0;\n\t\t}\n\t\tif (o2 instanceof Comparable) {\n\t\t\treturn ((Comparable)o2).compareTo(o1) > 0;\n\t\t}\n\t\tthrow new ELException(LocalMessages.get(\"error.compare.types\", o1.getClass(), o2.getClass()));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static final boolean gt0(TypeConverter converter, Object o1, Object o2) {\t\t\n\t\tClass<?> t1 = o1.getClass();\n\t\tClass<?> t2 = o2.getClass();\n\t\tif (BigDecimal.class.isAssignableFrom(t1) || BigDecimal.class.isAssignableFrom(t2)) {\n\t\t\treturn converter.convert(o1, BigDecimal.class).compareTo(converter.convert(o2, BigDecimal.class)) > 0;\n\t\t}\n\t\tif (SIMPLE_FLOAT_TYPES.contains(t1) || SIMPLE_FLOAT_TYPES.contains(t2)) {\n\t\t\treturn converter.convert(o1, Double.class) > converter.convert(o2, Double.class);\n\t\t}\n\t\tif (BigInteger.class.isAssignableFrom(t1) || BigInteger.class.isAssignableFrom(t2)) {\n\t\t\treturn converter.convert(o1, BigInteger.class).compareTo(converter.convert(o2, BigInteger.class)) > 0;\n\t\t}\n\t\tif (SIMPLE_INTEGER_TYPES.contains(t1) || SIMPLE_INTEGER_TYPES.contains(t2)) {\n\t\t\treturn converter.convert(o1, Long.class) > converter.convert(o2, Long.class);\n\t\t}","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/misc/BooleanOperations.java#L47-L83","documentation":"BooleanOperations.lt0 performs '<' comparison on two operands after numeric coercion attempts fail. If neither operand is Comparable (and types are otherwise incomparable), it throws an ELException with 'error.compare.types' naming both classes.","triggerScenarios":"Evaluating an EL comparison like a < b where a and b are non-numeric, non-Comparable objects (e.g. arbitrary POJOs, arrays), via lt() or ge().","commonSituations":"Comparing unrelated object types in an expression condition; a bean property's type changed between versions so a formerly Comparable value is now a POJO; comparing booleans or custom types without Comparable support.","solutions":["Make one of the operand types implement Comparable (with matching comparison semantics).","Coerce operands in the expression to comparable types (e.g. numbers or strings) before comparing.","Rewrite the expression condition to compare a comparable field instead of the objects themselves."],"exampleFix":"// before: #{user < otherUser}  (POJOs, not Comparable)\n// after: #{user.id < otherUser.id}\n// or make User implement Comparable<User> with compareTo comparing by id","handlingStrategy":"type-guard","validationCode":"boolean comparable(Object o) { return o == null || o instanceof Comparable || o instanceof Number || o instanceof String; }\nif (!comparable(a) || !comparable(b)) throw new IllegalArgumentException(\"operands of < must be Comparable\");","typeGuard":"boolean isComparableOperand(Object o) {\n    return o instanceof Comparable || o instanceof Number || o instanceof String;\n}","tryCatchPattern":"try {\n    boolean result = BooleanOperations.lt(converter, a, b);\n} catch (ELException e) {\n    // incomparable operand types; coerce or compare a comparable field instead\n}","preventionTips":["Ensure types used in <, >, <=, >= EL comparisons implement Comparable.","Compare scalar fields (id, date, amount) rather than whole objects.","Keep bean property types stable (Comparable) across refactors."],"tags":["el","comparison","type-mismatch","flowable"],"backgroundTag":"type-mismatch","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}