{"record":{"id":"25f10d76d9269c26","repo":"languagetool-org/languagetool","slug":"data-key-in-json-requires-either-text-or-anno","errorCode":null,"errorMessage":"'data' key in JSON requires either 'text' or 'annotation' key, not both","messagePattern":"'data' key in JSON requires either 'text' or 'annotation' key, not both","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/ApiV2.java","lineNumber":160,"sourceCode":"    ServerMetricsCollector.getInstance().logResponse(HttpURLConnection.HTTP_OK);\n  }\n\n  private void handleCheckRequest(HttpExchange httpExchange, Map<String, String> parameters, ErrorRequestLimiter errorRequestLimiter, String remoteAddress, HTTPServerConfig config) throws Exception {\n    AnnotatedText aText;\n    if (parameters.containsKey(\"text\") && parameters.containsKey(\"data\")) {\n      throw new BadRequestException(\"Set only 'text' or 'data' parameter, not both\");\n    } else if (parameters.containsKey(\"text\")) {\n      aText = new AnnotatedTextBuilder().addText(parameters.get(\"text\")).build();\n    } else if (parameters.containsKey(\"data\")) {\n      ObjectMapper mapper = new ObjectMapper();\n      JsonNode data;\n      try {\n        data = mapper.readTree(parameters.get(\"data\"));\n      } catch (JsonProcessingException e) {\n        throw new BadRequestException(\"Could not parse JSON from 'data' parameter\", e);\n      }\n      if (data.get(\"text\") != null && data.get(\"annotation\") != null) {\n        throw new BadRequestException(\"'data' key in JSON requires either 'text' or 'annotation' key, not both\");\n      } else if (data.get(\"text\") != null) {\n        aText = getAnnotatedTextFromString(data, data.get(\"text\").asText());\n      } else if (data.get(\"annotation\") != null) {\n        aText = getAnnotatedTextFromJson(data);\n      } else {\n        throw new BadRequestException(\"'data' key in JSON requires 'text' or 'annotation' key\");\n      }\n    } else {\n      throw new BadRequestException(\"Missing 'text' or 'data' parameter\");\n    }\n    //get from config\n    if (config.logIp && aText.getPlainText().trim().equals(config.logIpMatchingPattern)) {\n      handleIpLogMatch(httpExchange, remoteAddress, parameters);\n      //no need to check text again rules\n      return;\n    }\n    textChecker.checkText(aText, httpExchange, parameters, errorRequestLimiter, remoteAddress);\n  }","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/ApiV2.java#L142-L178","documentation":"The 'data' JSON payload of /v2/check may contain either a 'text' key (to be annotated per 'annotatedText' rules) or an 'annotation' key (explicit token list), but not both, since the two modes conflict. Supplying both keys throws BadRequestException (HTTP 400).","triggerScenarios":"POST /v2/check with data={\"text\":\"...\",\"annotation\":[...]} — both keys present in the parsed JSON object.","commonSituations":"Migrating clients that append an annotation array while keeping the legacy text field; template code that fills both keys conditionally.","solutions":["Remove one of the two keys — keep 'text' for builder-style input or 'annotation' for explicit tokens","If both pieces of info are needed, convert the text into the annotation format","Add a client-side check that at most one key is set before sending"],"exampleFix":"// before\n{\"text\":\"Hello world\",\"annotation\":[{\"text\":\"Hello\"},{\"text\":\"world\"}]}\n// after\n{\"annotation\":[{\"text\":\"Hello\"},{\"text\":\"world\"}]}","handlingStrategy":"validation","validationCode":"const d = JSON.parse(dataPayload);\nif (d.text != null && d.annotation != null) {\n    throw new Error(\"'text' and 'annotation' are mutually exclusive\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    const res = await check({ data: JSON.stringify(d) });\n} catch (e) {\n    if (/not both/.test(e.message)) {\n        console.error('Keep only text OR annotation in data JSON');\n    } else throw e;\n}","preventionTips":["Choose one data mode (builder-style text or explicit annotation) at design time","Add a schema check on the payload before each request","Document the mutually exclusive keys in client code"],"tags":["http","api","json","validation","languagetool-server"],"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"}