{"record":{"id":"161277ff18db9b7e","repo":"languagetool-org/languagetool","slug":"data-key-in-json-requires-text-or-annotation","errorCode":null,"errorMessage":"'data' key in JSON requires 'text' or 'annotation' key","messagePattern":"'data' key in JSON requires 'text' or 'annotation' key","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/ApiV2.java","lineNumber":166,"sourceCode":"      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  }\n\n  private void handleIpLogMatch(HttpExchange httpExchange, String remoteAddress, Map<String, String> parameters) {\n    Logger logger = LoggerFactory.getLogger(ApiV2.class);\n    InetSocketAddress localAddress = httpExchange.getLocalAddress();\n    logger.info(String.format(\"Found log-my-IP text in request from: %s to: %s, requestParams: %s\", remoteAddress, localAddress.toString(), parameters));\n  }","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/ApiV2.java#L148-L184","documentation":"If the 'data' JSON payload of /v2/check contains neither a 'text' nor an 'annotation' key, there is nothing to check, so the server throws BadRequestException (HTTP 400) with this message. Note this also fires when a key exists but maps to JSON null, since data.get(\"text\") returns null for null values.","triggerScenarios":"POST /v2/check with data={} or data={\"software\":\"...\"} (metadata-only JSON), or data={\"text\":null}.","commonSituations":"Clients sending only meta fields (e.g. username/dictionary hints) without the actual content; serialization dropping empty text fields.","solutions":["Add a 'text' or 'annotation' key with the content to check","Ensure the client serializes empty strings rather than dropping/nulling the text field","Validate the payload shape client-side before the request"],"exampleFix":"// before\n{\"username\":\"alice\"}\n// after\n{\"username\":\"alice\",\"annotation\":[{\"text\":\"Hello world\"}]}","handlingStrategy":"validation","validationCode":"const d = JSON.parse(dataPayload);\nif (d.text == null && d.annotation == null) {\n    throw new Error(\"data JSON needs a 'text' or 'annotation' key\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    const res = await check({ data: JSON.stringify(d) });\n} catch (e) {\n    if (/requires 'text' or 'annotation' key/.test(e.message)) {\n        console.error('data payload has no content to check');\n    } else throw e;\n}","preventionTips":["Validate data payload shape client-side before sending","Ensure serializers emit text/annotation even when empty rather than omitting them","Keep meta fields separate from the required content key"],"tags":["http","api","json","validation","languagetool-server"],"backgroundTag":"schema-validation-failed","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"}