{"record":{"id":"e8cce6d9c8fdefea","repo":"alibaba/spring-ai-alibaba","slug":"cannot-merge-incompatible-types-and","errorCode":null,"errorMessage":"Cannot merge incompatible types: {} and {}","messagePattern":"Cannot merge incompatible types: (.+?) and (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/state/strategy/MergeStrategy.java","lineNumber":59,"sourceCode":"\t\t\tnewValue = newValueOptional.orElse(null);\n\t\t}\n\n\t\tif (newValue == null) {\n\t\t\treturn oldValue;\n\t\t}\n\n\t\tif (oldValue == null) {\n\t\t\treturn newValue;\n\t\t}\n\n\t\tif (oldValue instanceof Map && newValue instanceof Map) {\n\t\t\tMap<Object, Object> mergedMap = new HashMap<>((Map<?, ?>) oldValue);\n\t\t\tmergedMap.putAll((Map<?, ?>) newValue);\n\t\t\treturn mergedMap;\n\t\t}\n\n\t\tif (oldValue.getClass() != newValue.getClass()) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t     \"Cannot merge incompatible types: \" +\n\t\t\t\t\t     oldValue.getClass().getName() + \" and \" + newValue.getClass().getName()\n\t\t\t);\n\t\t}\n\n\t\tif (shouldMergeObjectFields(oldValue.getClass())) {\n\t\t\treturn mergeObjectFields(oldValue, newValue).orElse(newValue);\n\t\t}\n\n\t\treturn newValue;\n\t}\n\n\tprivate Optional<Object> mergeObjectFields(Object oldValue, Object newValue) {\n\t\ttry {\n\t\t\tObject mergedValue = createInstance(oldValue.getClass());\n\t\t\tfor (Field field : mergeableFields(oldValue.getClass())) {\n\t\t\t\tfield.setAccessible(true);\n\t\t\t\tObject oldFieldValue = field.get(oldValue);","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/state/strategy/MergeStrategy.java#L41-L77","documentation":"The default apply() merge strategy merges maps generically, but for other values it requires oldValue and newValue to be the same class; if classes differ it throws IllegalArgumentException listing both class names. This guards state channel reducers from silently combining incompatible value shapes during graph state updates.","triggerScenarios":"Two graph nodes write to the same state channel in one superstep and return values of different types (e.g. a Map and a String, or Integer and Long); a custom reducer delegates to this strategy with heterogeneous values.","commonSituations":"Parallel/branching workflows where branches emit different result types into a shared key; refactoring a node's return type without updating other writers; numeric boxing differences (Integer vs Long) across nodes.","solutions":["Make all writers to the channel return the same type","Add a custom MergeStrategy/reducer for that channel that handles both types","Coerce/convert values to a common type before returning them from nodes"],"exampleFix":"// before\nstate.put(\"result\", \"text-summary\");   // node A\nstate.put(\"result\", Map.of(\"k\",\"v\")); // node B -> throws\n// after\n// both nodes emit the same shape, e.g.\nstate.put(\"result\", Map.of(\"summary\", \"text-summary\"));","handlingStrategy":"try-catch","validationCode":"if (oldValue != null && newValue != null && oldValue.getClass() != newValue.getClass()) { /* coerce or route to custom reducer first */ }","typeGuard":null,"tryCatchPattern":"try { merged = strategy.apply(channel, oldValue, newValue); } catch (IllegalArgumentException e) { log.error(\"Channel {} type conflict: {}\", channel, e.getMessage()); }","preventionTips":["Enforce a single result type per state channel","Document the channel schema for all node authors","Write custom reducers for heterogeneous channels"],"tags":["java","state","merge","graph","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}