languagetool-org/languagetool · error · BadRequestException
Mode must be one of 'textLevelOnly', 'allButTextLevelOnly',
Error message
Mode must be one of 'textLevelOnly', 'allButTextLevelOnly', or 'all' but was: '{modeParam}' What it means
ServerTools.getMode maps the 'mode' request parameter to JLanguageTool.Mode. Any value other than textLevelOnly, allButTextLevelOnly, all, or the undocumented 'batch' alias throws BadRequestException (HTTP 400) listing the valid values.
Source
Thrown at languagetool-server/src/main/java/org/languagetool/server/ServerTools.java:188
}
}
@NotNull
static Mode getMode(Map<String, String> params) {
Mode mode;
if (params.get("mode") != null) {
String modeParam = params.get("mode");
if ("textLevelOnly".equals(modeParam)) {
mode = Mode.TEXTLEVEL_ONLY;
} else if ("allButTextLevelOnly".equals(modeParam)) {
mode = Mode.ALL_BUT_TEXTLEVEL_ONLY;
} else if ("all".equals(modeParam)) {
mode = Mode.ALL;
} else if ("batch".equals(modeParam)) {
// used in undocumented API for /words/add, /words/delete; ignore
mode = Mode.ALL;
} else {
throw new BadRequestException("Mode must be one of 'textLevelOnly', 'allButTextLevelOnly', or 'all' but was: '" + modeParam + "'");
}
} else {
mode = Mode.ALL;
}
return mode;
}
@NotNull
static String getModeForLog(Mode mode) {
switch (mode) {
case TEXTLEVEL_ONLY: return "tlo";
case ALL_BUT_TEXTLEVEL_ONLY: return "!tlo";
case ALL: return "all";
default: return "?";
}
}
@NotNullView on GitHub (pinned to 2e990059ce)
Solutions
- Send exactly one of: mode=textLevelOnly, mode=allButTextLevelOnly, or mode=all (lowercase as shown)
- Fix casing — matching is case-sensitive via String.equals
- Remove the mode parameter entirely to get the default Mode.ALL
- Validate the mode value in the client before building the request
Example fix
// before "mode=textlevel" // after "mode=textLevelOnly"
Defensive patterns
Strategy: validation
Validate before calling
const VALID_MODES = ['textLevelOnly', 'allButTextLevelOnly', 'all'];
function validateMode(mode) {
if (mode !== undefined && !VALID_MODES.includes(mode)) {
throw new Error(`mode must be one of ${VALID_MODES.join(', ')}`);
}
} Try / catch
try {
return await lt.check({ ...params, mode });
} catch (e) {
if (e.status === 400 && /Mode must be one of/.test(e.message)) {
return await lt.check({ ...params, mode: undefined }); // default ALL
}
throw e;
} Prevention
- Define mode as a string-literal enum/union in client code
- Remember matching is case-sensitive; normalize with a mapping, not toLowerCase on the wire
- Test serialization of the mode value in CI
- Reuse the server's documented mode names verbatim
When it happens
Trigger: Calling /v2/check with mode=<invalid>, e.g. mode=textlevel (wrong case), mode=text-level, mode=allOnly, or a typo like mode=al.
Common situations: Case-sensitive matching overlooked ('textLevelOnly' vs 'textlevelonly'); API changes from older clients using removed mode names; building query strings from unchecked user input or dropdown values.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Unknown value '{param}' for parameter 'level'. Valid values:
- This end point needs a user id
- apiKey AND password was set, set only apiKey
- With 'username' set, you also need to specify 'apiKey'
- apiKey was set, but username was not: {apiKey}
AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06).
Data as JSON: /api/errors/2dfc1fe6b24475fd.
Report an issue: GitHub.