alibaba/spring-ai-alibaba · error · IllegalStateException

Edge '%s' is parallel

Error message

Edge '%s' is parallel

What it means

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.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/internal/edge/Edge.java:55

 * @param targets The targets value associated with the edge.
 */
public record Edge(String sourceId, List<EdgeValue> targets) {

	public Edge(String sourceId, EdgeValue target) {
		this(sourceId, List.of(target));
	}

	public Edge(String id) {
		this(id, List.of());
	}

	public boolean isParallel() {
		return targets.size() > 1;
	}

	public EdgeValue target() {
		if (isParallel()) {
			throw new IllegalStateException(format("Edge '%s' is parallel", sourceId));
		}
		return targets.get(0);
	}

	public boolean anyMatchByTargetId(String targetId) {
		return targets().stream()
			.anyMatch(v -> (v.id() != null) ? Objects.equals(v.id(), targetId)
					: v.value().mappings().containsValue(targetId)

			);
	}

	public Edge withSourceAndTargetIdsUpdated(Node node, Function<String, String> newSourceId,
			Function<String, EdgeValue> newTarget) {

		var newTargets = targets().stream().map(t -> t.withTargetIdsUpdated(newTarget)).toList();
		return new Edge(newSourceId.apply(sourceId), newTargets);

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Call edge.isParallel() first and use the targets list (EdgeValue with multiple entries) when it returns true.
  2. Use anyMatchByTargetId()/targets() APIs for parallel edges instead of target().
  3. Fix caller logic to branch per target when handling fan-out edges.

Example fix

// before
EdgeValue single = edge.target();
// after
if (edge.isParallel()) {
    edge.targets().forEach(t -> handle(t));
} else {
    handle(edge.target());
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Check edge arity before requesting a single target
if (edge.isParallel()) { throw new IllegalArgumentException("Use targets() for parallel edge " + edge.getSourceId()); }

Type guard

Optional<EdgeValue> singleTarget(Edge e) { return e.isParallel() ? Optional.empty() : Optional.of(e.target()); }

Try / catch

try { EdgeValue v = edge.target(); ... } catch (IllegalStateException e) { if (e.getMessage().endsWith("is parallel")) { edge.targets().forEach(this::handle); } }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/7ebf0e1ab0de68cb. Report an issue: GitHub.