languagetool-org/languagetool · error · BadRequestException
'data' key in JSON requires either 'text' or 'annotation' ke
Error message
'data' key in JSON requires either 'text' or 'annotation' key, not both
What it means
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).
Source
Thrown at languagetool-server/src/main/java/org/languagetool/server/ApiV2.java:160
ServerMetricsCollector.getInstance().logResponse(HttpURLConnection.HTTP_OK);
}
private void handleCheckRequest(HttpExchange httpExchange, Map<String, String> parameters, ErrorRequestLimiter errorRequestLimiter, String remoteAddress, HTTPServerConfig config) throws Exception {
AnnotatedText aText;
if (parameters.containsKey("text") && parameters.containsKey("data")) {
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);
}View on GitHub (pinned to 2e990059ce)
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
Example fix
// before
{"text":"Hello world","annotation":[{"text":"Hello"},{"text":"world"}]}
// after
{"annotation":[{"text":"Hello"},{"text":"world"}]} Defensive patterns
Strategy: validation
Validate before calling
const d = JSON.parse(dataPayload);
if (d.text != null && d.annotation != null) {
throw new Error("'text' and 'annotation' are mutually exclusive");
} Try / catch
try {
const res = await check({ data: JSON.stringify(d) });
} catch (e) {
if (/not both/.test(e.message)) {
console.error('Keep only text OR annotation in data JSON');
} else throw e;
} Prevention
- 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
When it happens
Trigger: POST /v2/check with data={"text":"...","annotation":[...]} — both keys present in the parsed JSON object.
Common situations: Migrating clients that append an annotation array while keeping the legacy text field; template code that fills both keys conditionally.
Related errors
- 'data' key in JSON requires 'text' or 'annotation' key
- 'language' parameter missing
- Set only 'text' or 'data' parameter, not both
- Could not parse JSON from 'data' parameter
- Only either 'text' or 'markup' are supported in an object in
AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06).
Data as JSON: /api/errors/25f10d76d9269c26.
Report an issue: GitHub.