{"record":{"id":"253f2e0d56da85a9","repo":"hibernate/hibernate-orm","slug":"result-type-of-all-operands-must-match","errorCode":null,"errorMessage":"Result type of all operands must match","messagePattern":"Result type of all operands must match","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java","lineNumber":770,"sourceCode":"\n\t@Override\n\tpublic <T> JpaSubQuery<T> except(boolean all, Subquery<? extends T> query1, Subquery<?>... queries) {\n\t\treturn setOperation( all ? SetOperator.EXCEPT_ALL : SetOperator.EXCEPT, query1, queries );\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate <T> JpaCriteriaQuery<T> setOperation(\n\t\t\tSetOperator operator,\n\t\t\tCriteriaQuery<? extends T> criteriaQuery,\n\t\t\tCriteriaQuery<?>... queries) {\n\t\tfinal var resultType = (Class<T>) criteriaQuery.getResultType();\n\t\tfinal List<SqmQueryPart<T>> queryParts = new ArrayList<>( queries.length + 1 );\n\t\tfinal Map<String, SqmCteStatement<?>> cteStatements = new LinkedHashMap<>();\n\t\tfinal var selectStatement = (SqmSelectStatement<T>) criteriaQuery;\n\t\tcollectQueryPartsAndCtes( selectStatement, queryParts, cteStatements );\n\t\tfor ( var query : queries ) {\n\t\t\tif ( query.getResultType() != resultType ) {\n\t\t\t\tthrow new IllegalArgumentException( \"Result type of all operands must match\" );\n\t\t\t}\n\t\t\tcollectQueryPartsAndCtes( (SqmSelectQuery<T>) query, queryParts, cteStatements );\n\t\t}\n\t\treturn new SqmSelectStatement<>(\n\t\t\t\tnew SqmQueryGroup<>( this, operator, queryParts ),\n\t\t\t\tresultType,\n\t\t\t\tcteStatements,\n\t\t\t\tselectStatement.getQuerySource(),\n\t\t\t\tthis\n\t\t);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate <T> JpaSubQuery<T> setOperation(\n\t\t\tSetOperator operator,\n\t\t\tSubquery<? extends T> subquery,\n\t\t\tSubquery<?>... queries) {\n\t\tfinal var resultType = (Class<T>) subquery.getResultType();","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java#L752-L788","documentation":"Thrown by HibernateCriteriaBuilder.union/unionAll/intersect/intersectAll/except/exceptAll(CriteriaQuery, CriteriaQuery...) when the operands' result types differ. The check uses reference equality on the Class returned by getResultType(), so both a genuinely different type (Long vs Integer) and the same class loaded by two different classloaders fail. Set operations in SQL require all operands to project union-compatible columns.","triggerScenarios":"cb.union(queryA, queryB) where queryA was built with select(Long.class) or multiselect to Long and queryB selects Integer.class; queries where one operand selects an entity (Order.class) and another selects a DTO class; same-named DTO classes loaded from different classloaders in app-server/reloading environments.","commonSituations":"Building 'search across two tables' queries where each sub-query maps to a different result DTO; count/aggregate operands that mix Long and Integer boxing; dev-reload or OSGi setups where the DTO class is loaded twice.","solutions":["Make every operand produce exactly the same result type: rebuild each query with matching q.select(...) / tuple(...) target class (e.g. cast numeric results with cb.sum(...).as(Long.class) so all operands are Long).","If operands naturally return different shapes, project them into one shared DTO via a ConstructorExpression (cb.construct(ResultDto.class, ...)) in every operand.","In classloader-sensitive environments, ensure the criteria builder and the DTO class come from the same classloader (build queries with the SessionFactory's own CriteriaBuilder)."],"exampleFix":"// before\nCriteriaQuery<Integer> q1 = cb.createQuery(Integer.class); q1.select(cb.count(root1).as(Integer.class));\nCriteriaQuery<Long> q2 = cb.createQuery(Long.class); q2.select(cb.count(root2));\ncb.union(q1, q2); // IllegalArgumentException: Result type of all operands must match\n\n// after\nCriteriaQuery<Long> q1 = cb.createQuery(Long.class); q1.select(cb.count(root1));\nCriteriaQuery<Long> q2 = cb.createQuery(Long.class); q2.select(cb.count(root2));\ncb.union(q1, q2);","handlingStrategy":"validation","validationCode":"boolean sameResultType(CriteriaQuery<?> first, CriteriaQuery<?>... rest) {\n    Class<?> t = first.getResultType();\n    for (CriteriaQuery<?> q : rest) if (q.getResultType() != t) return false;\n    return true;\n}\n// guard:\nif (!sameResultType(q1, q2, q3)) throw new IllegalArgumentException(\"operands must share one result type\");","typeGuard":"static boolean isUnionCompatible(CriteriaQuery<?> a, CriteriaQuery<?> b) {\n    return a.getResultType() == b.getResultType(); // reference equality mirrors Hibernate's check\n}","tryCatchPattern":"try {\n    cb.union(q1, q2);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Result type of all operands\")) { /* retype operands to one Class and retry */ }\n    else throw e;\n}","preventionTips":["Standardize every operand of a set operation on one result class from the start (e.g. always Long for counts).","Write shared builder helpers that take the common Class<T> and stamp it on every operand.","In classloader-heavy runtimes, always obtain the CriteriaBuilder from the same SessionFactory that loaded the result DTO."],"tags":["hibernate","criteria","set-operations","union","type-mismatch"],"backgroundTag":"set-operation-operand-mismatch","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}