{"record":{"id":"ab47003cd7c24ce8","repo":"theonedev/onedev","slug":"opposite-side-of-link-spec-is-not-multiple-and-the","errorCode":null,"errorMessage":"Opposite side of link spec is not multiple and the target issue is already linked to another issue via this link spec","messagePattern":"Opposite side of link spec is not multiple and the target issue is already linked to another issue via this link spec","errorType":"validation","errorClass":"ValidationException","httpStatus":null,"severity":"error","filePath":"server-core/src/main/java/io/onedev/server/model/IssueLink.java","lineNumber":100,"sourceCode":"\t\tthis.position = position;\n\t}\n\n\tpublic void validate() {\n\t\tif (getSource().equals(getTarget()))\n\t\t\tthrow new ValidationException(\"Cannot link to self\");\n\t\tif (getSpec().getOpposite() != null) {\n\t\t\tif (getSource().getTargetLinks().stream()\n\t\t\t\t\t.anyMatch(it -> it.getSpec().equals(getSpec()) && it.getTarget().equals(getTarget())))\n\t\t\t\tthrow new ValidationException(\"Source issue already linked to target issue via specified link spec\");\n\t\t\tif (!getSpec().isMultiple()\n\t\t\t\t\t&& getSource().getTargetLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())))\n\t\t\t\tthrow new ValidationException(\n\t\t\t\t\t\t\"Link spec is not multiple and the source issue is already linked to another issue via this link spec\");\n\t\t\tif (!getSpec().getParsedIssueQuery(getSource().getProject()).matches(getTarget()))\n\t\t\t\tthrow new ValidationException(\"Link spec not allowed to link to the target issue\");\n\t\t\tif (!getSpec().getOpposite().isMultiple()\n\t\t\t\t\t&& getTarget().getSourceLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())))\n\t\t\t\tthrow new ValidationException(\n\t\t\t\t\t\t\"Opposite side of link spec is not multiple and the target issue is already linked to another issue via this link spec\");\n\t\t\tif (!getSpec().getOpposite().getParsedIssueQuery(getSource().getProject())\n\t\t\t\t\t.matches(getSource()))\n\t\t\t\tthrow new ValidationException(\"Opposite side of link spec not allowed to link to the source issue\");\n\t\t} else {\n\t\t\tif (getSource().getLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())\n\t\t\t\t\t&& it.getLinked(getSource()).equals(getTarget())))\n\t\t\t\tthrow new ValidationException(\"Specified issues already linked via specified link spec\");\n\t\t\tif (!getSpec().isMultiple()\n\t\t\t\t\t&& getSource().getLinks().stream().anyMatch(it -> it.getSpec().equals(getSpec())))\n\t\t\t\tthrow new ValidationException(\n\t\t\t\t\t\t\"Link spec is not multiple and source issue is already linked to another issue via this link spec\");\n\t\t\tvar parsedIssueQuery = getSpec().getParsedIssueQuery(getSource().getProject());\n\t\t\tif (!parsedIssueQuery.matches(getSource()) || !parsedIssueQuery.matches(getTarget()))\n\t\t\t\tthrow new ValidationException(\"Link spec not allowed to link specified issues\");\n\t\t}\n\t}\n}","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/model/IssueLink.java#L82-L118","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the target's existing link of this spec before creating the new one.","Enable 'Multiple' on the opposite side of the link spec in project link settings.","Choose a target issue that currently has no link of this spec."],"exampleFix":"// before\nIssueLink.createLink(link); // target already linked elsewhere via opposite spec\n// after\nIssueLink conflicting = tgt.getSourceLinks().stream().filter(l -> l.getSpec().equals(spec)).findFirst().orElse(null);\nif (conflicting != null) conflicting.remove();\nIssueLink.createLink(link);","handlingStrategy":"validation","validationCode":"boolean targetFree = link.getSpec().getOpposite().isMultiple() || link.getTarget().getSourceLinks().stream().noneMatch(l -> l.getSpec().equals(link.getSpec()));\nif (!targetFree) { /* pick another target or unlink */ }","typeGuard":null,"tryCatchPattern":"try { IssueLink.createLink(link); } catch (ValidationException e) { // unlink target's existing link of the spec, then retry }","preventionTips":["Check both endpoints' existing links before creating a link","Enable Multiple on both sides for many-to-many semantics","Make link automation idempotent and pre-clean conflicts"],"tags":["issue-links","validation","multiplicity"],"backgroundTag":"invalid-state-transition","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}