{"record":{"id":"7ebf0e1ab0de68cb","repo":"alibaba/spring-ai-alibaba","slug":"edge-s-is-parallel","errorCode":null,"errorMessage":"Edge '%s' is parallel","messagePattern":"Edge '(.+?)' is parallel","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/internal/edge/Edge.java","lineNumber":55,"sourceCode":" * @param targets The targets value associated with the edge.\n */\npublic record Edge(String sourceId, List<EdgeValue> targets) {\n\n\tpublic Edge(String sourceId, EdgeValue target) {\n\t\tthis(sourceId, List.of(target));\n\t}\n\n\tpublic Edge(String id) {\n\t\tthis(id, List.of());\n\t}\n\n\tpublic boolean isParallel() {\n\t\treturn targets.size() > 1;\n\t}\n\n\tpublic EdgeValue target() {\n\t\tif (isParallel()) {\n\t\t\tthrow new IllegalStateException(format(\"Edge '%s' is parallel\", sourceId));\n\t\t}\n\t\treturn targets.get(0);\n\t}\n\n\tpublic boolean anyMatchByTargetId(String targetId) {\n\t\treturn targets().stream()\n\t\t\t.anyMatch(v -> (v.id() != null) ? Objects.equals(v.id(), targetId)\n\t\t\t\t\t: v.value().mappings().containsValue(targetId)\n\n\t\t\t);\n\t}\n\n\tpublic Edge withSourceAndTargetIdsUpdated(Node node, Function<String, String> newSourceId,\n\t\t\tFunction<String, EdgeValue> newTarget) {\n\n\t\tvar newTargets = targets().stream().map(t -> t.withTargetIdsUpdated(newTarget)).toList();\n\t\treturn new Edge(newSourceId.apply(sourceId), newTargets);\n","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/internal/edge/Edge.java#L37-L73","documentation":"Edge.target() returns the single EdgeValue of a non-parallel edge. If the edge actually has multiple targets (a parallel/fan-out edge), it throws IllegalStateException \"Edge '<sourceId>' is parallel\" because asking for one target of a multi-target edge is meaningless.","triggerScenarios":"Calling edgeValue.target() (or APIs like findPathEnd that rely on it) on an edge created with multiple targets, e.g. via addEdge(\"a\", List.of(...)) or conditional edges resolving to several targets.","commonSituations":"Custom graph traversal code that assumes one target per edge; utility code walking edges after a fork was introduced; version changes where an edge previously single-target became parallel.","solutions":["Call edge.isParallel() first and use the targets list (EdgeValue with multiple entries) when it returns true.","Use anyMatchByTargetId()/targets() APIs for parallel edges instead of target().","Fix caller logic to branch per target when handling fan-out edges."],"exampleFix":"// before\nEdgeValue single = edge.target();\n// after\nif (edge.isParallel()) {\n    edge.targets().forEach(t -> handle(t));\n} else {\n    handle(edge.target());\n}","handlingStrategy":"type-guard","validationCode":"// Check edge arity before requesting a single target\nif (edge.isParallel()) { throw new IllegalArgumentException(\"Use targets() for parallel edge \" + edge.getSourceId()); }","typeGuard":"Optional<EdgeValue> singleTarget(Edge e) { return e.isParallel() ? Optional.empty() : Optional.of(e.target()); }","tryCatchPattern":"try { EdgeValue v = edge.target(); ... } catch (IllegalStateException e) { if (e.getMessage().endsWith(\"is parallel\")) { edge.targets().forEach(this::handle); } }","preventionTips":["Always call isParallel() before target()","Prefer targets()/anyMatchByTargetId() in generic traversal code","Re-check edge assumptions after adding fan-out branches"],"tags":["graph-structure","edges","unsupported-operation"],"backgroundTag":"unsupported-operation","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"}