theonedev/onedev · error · ValidationException

Opposite side of link spec is not multiple and the target is

Error message

Opposite side of link spec is not multiple and the target issue is already linked to another issue via this link spec

What it means

For a bidirectional link spec, validate() also enforces the OPPOSITE side's multiplicity: if the opposite spec isMultiple() is false, the target issue may have at most one link of this spec on its source side. This is thrown when the target already has a source link with the same spec pointing at another issue.

Source

Thrown at server-core/src/main/java/io/onedev/server/model/IssueLink.java:100

		this.position = position;
	}

	public void validate() {
		if (getSource().equals(getTarget()))
			throw new ValidationException("Cannot link to self");
		if (getSpec().getOpposite() != null) {
			if (getSource().getTargetLinks().stream()
					.anyMatch(it -> it.getSpec().equals(getSpec()) && it.getTarget().equals(getTarget())))
				throw new ValidationException("Source issue already linked to target issue via specified link spec");
			if (!getSpec().isMultiple()
					&& getSource().getTargetLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())))
				throw new ValidationException(
						"Link spec is not multiple and the source issue is already linked to another issue via this link spec");
			if (!getSpec().getParsedIssueQuery(getSource().getProject()).matches(getTarget()))
				throw new ValidationException("Link spec not allowed to link to the target issue");
			if (!getSpec().getOpposite().isMultiple()
					&& getTarget().getSourceLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())))
				throw new ValidationException(
						"Opposite side of link spec is not multiple and the target issue is already linked to another issue via this link spec");
			if (!getSpec().getOpposite().getParsedIssueQuery(getSource().getProject())
					.matches(getSource()))
				throw new ValidationException("Opposite side of link spec not allowed to link to the source issue");
		} else {
			if (getSource().getLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())
					&& it.getLinked(getSource()).equals(getTarget())))
				throw new ValidationException("Specified issues already linked via specified link spec");
			if (!getSpec().isMultiple()
					&& getSource().getLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())))
				throw new ValidationException(
						"Link spec is not multiple and source issue is already linked to another issue via this link spec");
			var parsedIssueQuery = getSpec().getParsedIssueQuery(getSource().getProject());
			if (!parsedIssueQuery.matches(getSource()) || !parsedIssueQuery.matches(getTarget()))
				throw new ValidationException("Link spec not allowed to link specified issues");
		}
	}
}

View on GitHub (pinned to d44925c47c)

Solutions

  1. Remove the target's existing link of this spec before creating the new one.
  2. Enable 'Multiple' on the opposite side of the link spec in project link settings.
  3. Choose a target issue that currently has no link of this spec.

Example fix

// before
IssueLink.createLink(link); // target already linked elsewhere via opposite spec
// after
IssueLink conflicting = tgt.getSourceLinks().stream().filter(l -> l.getSpec().equals(spec)).findFirst().orElse(null);
if (conflicting != null) conflicting.remove();
IssueLink.createLink(link);
Defensive patterns

Strategy: validation

Validate before calling

boolean targetFree = link.getSpec().getOpposite().isMultiple() || link.getTarget().getSourceLinks().stream().noneMatch(l -> l.getSpec().equals(link.getSpec()));
if (!targetFree) { /* pick another target or unlink */ }

Try / catch

try { IssueLink.createLink(link); } catch (ValidationException e) { // unlink target's existing link of the spec, then retry }

Prevention

When it happens

Trigger: createLink called where getSpec().getOpposite().isMultiple() == false and getTarget().getSourceLinks() already contains a link with an equal spec whose target-side owner (linked source) differs from the requested source issue.

Common situations: Symmetric case of error 340 on the target end: trying to make a target be 'blocks' for two issues while the opposite side is single-capacity; specs toggled to non-multiple after data was created; batch importers linking one issue as parent of several children when the spec only allows one.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/ab47003cd7c24ce8. Report an issue: GitHub.