theonedev/onedev · error · ValidationException

Source issue already linked to target issue via specified li

Error message

Source issue already linked to target issue via specified link spec

What it means

IssueLink.validate also rejects duplicate or conflicting links when the spec has an opposite (bidirectional) definition. If the source issue already has a link via this spec to the same target, ValidationException 'Source issue already linked to target issue via specified link spec' is thrown.

Source

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

	public Issue getLinked(Issue issue) {
		return issue.equals(source)?target:source;
	}

	public int getPosition() {
		return position;
	}

	public void setPosition(int position) {
		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()

View on GitHub (pinned to d44925c47c)

Solutions

  1. Check for an existing link with the same spec and target before creating (query source.getTargetLinks())
  2. Make link creation idempotent in scripts by skipping existing pairs
  3. Fix double-submit in UI/automation (disable button, dedupe requests)

Example fix

// before
issueLinkService.createLink(source, target, spec);  // may duplicate
// after
boolean exists = source.getTargetLinks().stream().anyMatch(l -> l.getSpec().equals(spec) && l.getTarget().equals(target));
if (!exists) issueLinkService.createLink(source, target, spec);
Defensive patterns

Strategy: validation

Validate before calling

boolean exists = source.getTargetLinks().stream().anyMatch(l -> l.getSpec().equals(spec) && l.getTarget().equals(target));

Try / catch

try { createLink(source, target, spec); } catch (ValidationException e) { /* treat as already-linked, continue */ }

Prevention

When it happens

Trigger: Calling createLink twice for the same source/target/spec pair; concurrent automation both creating the same link; retried import scripts re-running link creation.

Common situations: Idempotency issues in bulk import scripts; UI double-submit creating the link twice; automation that recreates links after issue edits without checking existing links.

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/2c3dd436db0588e3. Report an issue: GitHub.