{"record":{"id":"43aaa83debfded35","repo":"languagetool-org/languagetool","slug":"only-either-text-or-markup-are-supported-in-an","errorCode":null,"errorMessage":"Only either 'text' or 'markup' are supported in an object in 'annotation' list, not both: <node>","messagePattern":"Only either 'text' or 'markup' are supported in an object in 'annotation' list, not both: <node>","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/ApiV2.java","lineNumber":484,"sourceCode":"        }\n      }\n    }\n    return textBuilder.build();\n  }\n\n  private AnnotatedText getAnnotatedTextFromJson(JsonNode data) {\n    AnnotatedTextBuilder atb = new AnnotatedTextBuilder();\n    // Expected format:\n    // annotation: [\n    //   {text: 'text'},\n    //   {markup: '<b>'}\n    //   {text: 'more text'},\n    //   {markup: '</b>'}\n    // ]\n    //\n    for (JsonNode node : data.get(\"annotation\")) {\n      if (node.get(\"text\") != null && node.get(\"markup\") != null) {\n        throw new BadRequestException(\"Only either 'text' or 'markup' are supported in an object in 'annotation' list, not both: \" + node);\n      } else if (node.get(\"text\") != null && node.get(\"interpretAs\") != null) {\n        throw new BadRequestException(\"'text' cannot be used with 'interpretAs' (only 'markup' can): \" + node);\n      } else if (node.get(\"text\") != null) {\n        atb.addText(node.get(\"text\").asText());\n      } else if (node.get(\"markup\") != null) {\n        if (node.get(\"interpretAs\") != null) {\n          atb.addMarkup(node.get(\"markup\").asText(), node.get(\"interpretAs\").asText());\n        } else {\n          atb.addMarkup(node.get(\"markup\").asText());\n        }\n      } else {\n        throw new BadRequestException(\"Only 'text' and 'markup' are supported in 'annotation' list: \" + node);\n      }\n    }\n    return atb.build();\n  }\n\n  String getLanguages() throws IOException {","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/ApiV2.java#L466-L502","documentation":"When building AnnotatedText from the JSON 'annotation' list, each object may specify 'text' or 'markup', not both. LanguageTool throws BadRequestException when a single annotation object contains both keys, because it cannot decide whether the content is checkable text or non-checked markup.","triggerScenarios":"POSTing to /v2/check with data JSON like {\"annotation\":[{\"text\":\"Hello\",\"markup\":\"<b>\"}]} — any annotation object having both a non-null 'text' and a non-null 'markup' key.","commonSituations":"Clients programmatically generating the annotation list that accidentally include markup alongside text; hand-written payloads where markup was pasted into a text object.","solutions":["Split the object into two entries: one {\"markup\":...} followed by one {\"text\":...}.","Remove the 'markup' key if the content should be checked as text.","Remove the 'text' key if the content is markup; use 'interpretAs' with markup if it should be checked as text."],"exampleFix":"// before\n{\"annotation\":[{\"text\":\"Hello\",\"markup\":\"<b>\"}]}\n// after\n{\"annotation\":[{\"markup\":\"<b>\"},{\"text\":\"Hello\"}]}","handlingStrategy":"validation","validationCode":"for (const n of annotation) {\n  if ('text' in n && 'markup' in n) throw new Error('Annotation object must not have both text and markup: ' + JSON.stringify(n));\n}","typeGuard":"function isTextOnly(n) { return n.text != null && n.markup == null && n.interpretAs == null; }\nfunction isMarkupOnly(n) { return n.markup != null && n.text == null; }","tryCatchPattern":"try {\n  const res = await fetch('/v2/check', { method: 'POST', body: form });\n  if (!res.ok) throw new Error((await res.json()).message);\n} catch (e) {\n  console.error('annotation payload invalid:', e.message);\n}","preventionTips":["Build annotation objects with a single discriminated key per entry.","Validate the annotation JSON schema client-side before sending.","Split mixed content into consecutive markup/text entries."],"tags":["http","api","json","validation","languagetool"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}