languagetool-org/languagetool · error · BadRequestException

'data' key in JSON requires 'text' or 'annotation' key

Error message

'data' key in JSON requires 'text' or 'annotation' key

What it means

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.

Source

Thrown at languagetool-server/src/main/java/org/languagetool/server/ApiV2.java:166

      throw new BadRequestException("Set only 'text' or 'data' parameter, not both");
    } else if (parameters.containsKey("text")) {
      aText = new AnnotatedTextBuilder().addText(parameters.get("text")).build();
    } else if (parameters.containsKey("data")) {
      ObjectMapper mapper = new ObjectMapper();
      JsonNode data;
      try {
        data = mapper.readTree(parameters.get("data"));
      } catch (JsonProcessingException e) {
        throw new BadRequestException("Could not parse JSON from 'data' parameter", e);
      }
      if (data.get("text") != null && data.get("annotation") != null) {
        throw new BadRequestException("'data' key in JSON requires either 'text' or 'annotation' key, not both");
      } else if (data.get("text") != null) {
        aText = getAnnotatedTextFromString(data, data.get("text").asText());
      } else if (data.get("annotation") != null) {
        aText = getAnnotatedTextFromJson(data);
      } else {
        throw new BadRequestException("'data' key in JSON requires 'text' or 'annotation' key");
      }
    } else {
      throw new BadRequestException("Missing 'text' or 'data' parameter");
    }
    //get from config
    if (config.logIp && aText.getPlainText().trim().equals(config.logIpMatchingPattern)) {
      handleIpLogMatch(httpExchange, remoteAddress, parameters);
      //no need to check text again rules
      return;
    }
    textChecker.checkText(aText, httpExchange, parameters, errorRequestLimiter, remoteAddress);
  }

  private void handleIpLogMatch(HttpExchange httpExchange, String remoteAddress, Map<String, String> parameters) {
    Logger logger = LoggerFactory.getLogger(ApiV2.class);
    InetSocketAddress localAddress = httpExchange.getLocalAddress();
    logger.info(String.format("Found log-my-IP text in request from: %s to: %s, requestParams: %s", remoteAddress, localAddress.toString(), parameters));
  }

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Add a 'text' or 'annotation' key with the content to check
  2. Ensure the client serializes empty strings rather than dropping/nulling the text field
  3. Validate the payload shape client-side before the request

Example fix

// before
{"username":"alice"}
// after
{"username":"alice","annotation":[{"text":"Hello world"}]}
Defensive patterns

Strategy: validation

Validate before calling

const d = JSON.parse(dataPayload);
if (d.text == null && d.annotation == null) {
    throw new Error("data JSON needs a 'text' or 'annotation' key");
}

Try / catch

try {
    const res = await check({ data: JSON.stringify(d) });
} catch (e) {
    if (/requires 'text' or 'annotation' key/.test(e.message)) {
        console.error('data payload has no content to check');
    } else throw e;
}

Prevention

When it happens

Trigger: POST /v2/check with data={} or data={"software":"..."} (metadata-only JSON), or data={"text":null}.

Common situations: Clients sending only meta fields (e.g. username/dictionary hints) without the actual content; serialization dropping empty text fields.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06). Data as JSON: /api/errors/161277ff18db9b7e. Report an issue: GitHub.