{"record":{"id":"f98fdc58e115529a","repo":"theonedev/onedev","slug":"link-spec-is-not-multiple-and-the-source-issue-is","errorCode":null,"errorMessage":"Link spec is not multiple and the source issue is already linked to another issue via this link spec","messagePattern":"Link spec is not multiple and the source 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":94,"sourceCode":"\n\tpublic int getPosition() {\n\t\treturn position;\n\t}\n\n\tpublic void setPosition(int position) {\n\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\");","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/model/IssueLink.java#L76-L112","documentation":"IssueLink.validate() enforces the constraints configured on a LinkSpec before an issue link is persisted. When the spec has an opposite (bidirectional) spec and isMultiple() is false, a source issue may hold at most one link of that spec. This ValidationException is thrown when the source issue already has a link with the same spec pointing at a different target issue.","triggerScenarios":"Calling IssueLink.createLink (or the REST/UI link-issue operation) where getSpec().getOpposite() != null, getSpec().isMultiple() == false, and getSource().getTargetLinks() already contains a link with an equal spec whose target differs from the requested target.","commonSituations":"Admin defined a 'is blocked by / blocks' link as single-link (not multiple); user tries to add a second blocker to an issue; automation scripts or importers create several links of a single-capacity spec in a loop and hit the second iteration; project settings changed the spec to non-multiple after multiple links already existed.","solutions":["Check the existing links of the source issue for this spec and delete/unlink the old link before creating the new one (or link the new target and remove the old).","Edit the link spec in Administration > Links and enable 'Multiple' if an issue should legitimately have several links of this type.","If running an import/automation, catch ValidationException per pair and skip or pre-clean duplicates.","Use a spec with isMultiple()==true for many-to-many relationships like 'relates to'."],"exampleFix":"// before\nissueLink = new IssueLink(); issueLink.setSource(src); issueLink.setTarget(tgt); issueLink.setSpec(singleSpec); IssueLink.createLink(issueLink);\n// after\nIssueLink existing = src.getTargetLinks().stream().filter(l -> l.getSpec().equals(singleSpec)).findFirst().orElse(null);\nif (existing != null) existing.remove();\nIssueLink.createLink(issueLink);","handlingStrategy":"validation","validationCode":"boolean canLink = issueLink.getSpec().isMultiple() || issueLink.getSource().getTargetLinks().stream().noneMatch(l -> l.getSpec().equals(issueLink.getSpec()));\nif (!canLink) { /* unlink existing link or abort */ }","typeGuard":null,"tryCatchPattern":"try { IssueLink.createLink(issueLink); } catch (ValidationException e) { // remove existing link of this spec and retry once, or surface to user }","preventionTips":["Prefer multiple=true for specs like 'relates to' or 'blocks'","Before linking, scan the issue's links for the spec and replace rather than add","In batch imports, deduplicate (source, spec) pairs first"],"tags":["issue-links","validation","onedev"],"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-14T05:17:10.506Z"}