{"record":{"id":"e4b0c176d08faef0","repo":"hibernate/hibernate-orm","slug":"operand-of-op-getoperatorsqltext-is-of-t-e4b0c1","errorCode":null,"errorMessage":"Operand of \" + op.getOperatorSqlText() + \" is of type '\" + rightNodeType.getTypeName() + \"' which is not a numeric type (it is not an instance of 'java.lang.Number')","messagePattern":"Operand of \" \\+ op\\.getOperatorSqlText\\(\\) \\+ \" is of type '\" \\+ rightNodeType\\.getTypeName\\(\\) \\+ \"' which is not a numeric type \\(it is not an instance of 'java\\.lang\\.Number'\\)","errorType":"exception","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java","lineNumber":525,"sourceCode":"\t\tif ( leftNodeType != null && rightNodeType != null ) {\n\t\t\tfinal var leftJavaType = leftNodeType.getRelationalJavaType().getJavaTypeClass();\n\t\t\tfinal var rightJavaType = rightNodeType.getRelationalJavaType().getJavaTypeClass();\n\t\t\tif ( Number.class.isAssignableFrom( leftJavaType ) ) {\n\t\t\t\t// left operand is a number\n\t\t\t\tswitch (op) {\n\t\t\t\t\tcase MULTIPLY:\n\t\t\t\t\t\tif ( !Number.class.isAssignableFrom( rightJavaType )\n\t\t\t\t\t\t\t\t// we can scale a duration by a number\n\t\t\t\t\t\t\t\t&& !TemporalAmount.class.isAssignableFrom( rightJavaType ) ) {\n\t\t\t\t\t\t\tthrow new SemanticException(\n\t\t\t\t\t\t\t\t\t\"Operand of \" + op.getOperatorSqlText() + \" is of type '\" + rightNodeType.getTypeName() +\n\t\t\t\t\t\t\t\t\t\t\t\"' which is not a numeric type (it is not an instance of 'java.lang.Number' or 'java.time.TemporalAmount')\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tif ( !Number.class.isAssignableFrom( rightJavaType ) ) {\n\t\t\t\t\t\t\tthrow new SemanticException(\n\t\t\t\t\t\t\t\t\t\"Operand of \" + op.getOperatorSqlText() + \" is of type '\" + rightNodeType.getTypeName() +\n\t\t\t\t\t\t\t\t\t\t\t\"' which is not a numeric type (it is not an instance of 'java.lang.Number')\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if ( TemporalAmount.class.isAssignableFrom( leftJavaType ) ) {\n\t\t\t\t// left operand is a duration\n\t\t\t\tswitch (op) {\n\t\t\t\t\tcase ADD:\n\t\t\t\t\tcase SUBTRACT:\n\t\t\t\t\t\t// we can add/subtract durations\n\t\t\t\t\t\tif ( !TemporalAmount.class.isAssignableFrom( rightJavaType ) ) {\n\t\t\t\t\t\t\tthrow new SemanticException(\n\t\t\t\t\t\t\t\t\t\"Operand of \" + op.getOperatorSqlText() + \" is of type '\" + rightNodeType.getTypeName() +\n\t\t\t\t\t\t\t\t\t\t\t\"' which is not a temporal amount (it is not an instance of 'java.time.TemporalAmount')\"\n\t\t\t\t\t\t\t);","sourceCodeStart":507,"sourceCodeEnd":543,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java#L507-L543","documentation":"TypecheckUtil.assertOperands checks binary arithmetic: for every operator except MULTIPLY (+, -, /, %), when the left operand is a Number the right operand must also be a Number. Unlike '*', no TemporalAmount exemption exists, so `1 + duration` is rejected too. The query fails at semantic-analysis time with this SemanticException.","triggerScenarios":"HQL `e.salary + e.bonusCode` (String right side); `e.qty - e.active` (Boolean); `e.total / e.name`; `1 + x.day` or `5 + e.duration` (adding a number to a duration — the duration must be the LEFT operand for +, and only durations may be added to durations).","commonSituations":"Trying SQL-style string concatenation with '+' (HQL/JPQL requires concat() or ||); numeric data kept in text columns; adding numbers to `n day` style duration literals; generic query builders applying +/- to untyped attributes; stricter checks appearing when upgrading to Hibernate 6.3+.","solutions":["For string concatenation use `concat(a, b, c)` or `a || b` instead of '+'","Cast string operands to a numeric type: `e.salary + cast(e.bonusCode as double)`","For number-with-duration math, put the number on the LEFT and use '*' (`2 * e.duration`), or keep duration + duration","Fix the attribute mapping so arithmetic columns are numeric types"],"exampleFix":"// before (e.tax is String)\nselect e.amount + e.tax from Invoice e\n\n// after\nselect e.amount + cast(e.tax as double) from Invoice e","handlingStrategy":"validation","validationCode":"// Dynamic '+': only emit arithmetic when both sides are Number, else use concat\nObject l = attrJavaType(leftAttr), r = attrJavaType(rightAttr);\nString expr = (Number.class.isAssignableFrom((Class<?>) l)\n            && Number.class.isAssignableFrom((Class<?>) r))\n    ? leftAttr + \" + \" + rightAttr\n    : \"concat(\" + leftAttr + \", ' ', \" + rightAttr + \")\";","typeGuard":"static boolean bothNumeric(Class<?> a, Class<?> b) {\n    return Number.class.isAssignableFrom(a) && Number.class.isAssignableFrom(b);\n}","tryCatchPattern":"try {\n    em.createQuery(hql).executeUpdate();\n} catch (org.hibernate.query.SemanticException e) {\n    log.error(\"Operand type mismatch in + expression: {}\", hql, e);\n    throw e;\n}","preventionTips":["Never use '+' for string concatenation in HQL/JPQL — always concat() or ||","Remember number + duration is invalid; scale durations as `n * duration`","Add an integration test per stored query so operand-type regressions surface at build time"],"tags":["hql","jpql","type-checking","arithmetic","hibernate-6","semantic-exception"],"backgroundTag":"query-operand-type-mismatch","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}