languagetool-org/languagetool · error · BadRequestException

Only 'text' and 'markup' are supported in 'annotation' list:

Error message

Only 'text' and 'markup' are supported in 'annotation' list: 

What it means

Each object in the 'annotation' list must have a non-null 'text' or 'markup' key. If a node has neither (e.g. only 'interpretAs' or an unrelated/empty object), LanguageTool throws BadRequestException since it cannot interpret the entry.

Source

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

    //   {text: 'more text'},
    //   {markup: '</b>'}
    // ]
    //
    for (JsonNode node : data.get("annotation")) {
      if (node.get("text") != null && node.get("markup") != null) {
        throw new BadRequestException("Only either 'text' or 'markup' are supported in an object in 'annotation' list, not both: " + node);
      } else if (node.get("text") != null && node.get("interpretAs") != null) {
        throw new BadRequestException("'text' cannot be used with 'interpretAs' (only 'markup' can): " + node);
      } else if (node.get("text") != null) {
        atb.addText(node.get("text").asText());
      } else if (node.get("markup") != null) {
        if (node.get("interpretAs") != null) {
          atb.addMarkup(node.get("markup").asText(), node.get("interpretAs").asText());
        } else {
          atb.addMarkup(node.get("markup").asText());
        }
      } else {
        throw new BadRequestException("Only 'text' and 'markup' are supported in 'annotation' list: " + node);
      }
    }
    return atb.build();
  }

  String getLanguages() throws IOException {
    StringWriter sw = new StringWriter();
    try (JsonGenerator g = factory.createGenerator(sw)) {
      g.writeStartArray();
      List<Language> languages = new ArrayList<>(Languages.get());
      Set<String> longCodes = new HashSet<>();
      for (Language lang : languages) {
        g.writeStartObject();
        g.writeStringField("name", lang.getName());
        g.writeStringField("code", lang.getShortCode());
        g.writeStringField("longCode", lang.getShortCodeWithCountryAndVariant());
        longCodes.add(lang.getShortCodeWithCountryAndVariant());
        g.writeEndObject();

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Ensure every annotation object has exactly one of 'text' or 'markup'.
  2. Remove empty or malformed objects from the list.
  3. Inspect the offending node echoed in the message to find the malformed entry.

Example fix

// before
{"annotation":[{"interpretAs":"x"}]}
// after
{"annotation":[{"text":"x"}]}
Defensive patterns

Strategy: validation

Validate before calling

annotation.forEach((n, i) => {
  if (n.text == null && n.markup == null) throw new Error(`annotation[${i}] needs 'text' or 'markup'`);
});

Type guard

function isValidAnnotationNode(n) {
  return n != null && (n.text != null || n.markup != null);
}

Try / catch

try {
  const res = await fetch('/v2/check', { method: 'POST', body: form });
  if (res.status === 400) throw new Error(await res.text());
} catch (e) {
  console.error('invalid annotation entry:', e.message);
}

Prevention

When it happens

Trigger: POSTing data with annotation objects like {} or {"interpretAs":"foo"} or {"other":"x"} to /v2/check.

Common situations: Serialization bugs that drop the 'text'/'markup' key when its value is empty; schema drift between client and API versions.

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/7fb519ca4ca6c2be. Report an issue: GitHub.