{"record":{"id":"2dfc1fe6b24475fd","repo":"languagetool-org/languagetool","slug":"mode-must-be-one-of-textlevelonly-allbuttextle","errorCode":null,"errorMessage":"Mode must be one of 'textLevelOnly', 'allButTextLevelOnly', or 'all' but was: '{modeParam}'","messagePattern":"Mode must be one of 'textLevelOnly', 'allButTextLevelOnly', or 'all' but was: '(.+?)'","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/ServerTools.java","lineNumber":188,"sourceCode":"    }\n  }\n\n  @NotNull\n  static Mode getMode(Map<String, String> params) {\n    Mode mode;\n    if (params.get(\"mode\") != null) {\n      String modeParam = params.get(\"mode\");\n      if (\"textLevelOnly\".equals(modeParam)) {\n        mode = Mode.TEXTLEVEL_ONLY;\n      } else if (\"allButTextLevelOnly\".equals(modeParam)) {\n        mode = Mode.ALL_BUT_TEXTLEVEL_ONLY;\n      } else if (\"all\".equals(modeParam)) {\n        mode = Mode.ALL;\n      } else if (\"batch\".equals(modeParam)) {\n        // used in undocumented API for /words/add, /words/delete; ignore\n        mode = Mode.ALL;\n      } else {\n        throw new BadRequestException(\"Mode must be one of 'textLevelOnly', 'allButTextLevelOnly', or 'all' but was: '\" + modeParam + \"'\");\n      }\n    } else {\n      mode = Mode.ALL;\n    }\n    return mode;\n  }\n\n  @NotNull\n  static String getModeForLog(Mode mode) {\n    switch (mode) {\n      case TEXTLEVEL_ONLY: return \"tlo\";\n      case ALL_BUT_TEXTLEVEL_ONLY: return \"!tlo\";\n      case ALL: return \"all\";\n      default: return \"?\";\n    }\n  }\n\n  @NotNull","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/ServerTools.java#L170-L206","documentation":"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.","triggerScenarios":"Calling /v2/check with mode=<invalid>, e.g. mode=textlevel (wrong case), mode=text-level, mode=allOnly, or a typo like mode=al.","commonSituations":"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.","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"],"exampleFix":"// before\n\"mode=textlevel\"\n// after\n\"mode=textLevelOnly\"","handlingStrategy":"validation","validationCode":"const VALID_MODES = ['textLevelOnly', 'allButTextLevelOnly', 'all'];\nfunction validateMode(mode) {\n  if (mode !== undefined && !VALID_MODES.includes(mode)) {\n    throw new Error(`mode must be one of ${VALID_MODES.join(', ')}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await lt.check({ ...params, mode });\n} catch (e) {\n  if (e.status === 400 && /Mode must be one of/.test(e.message)) {\n    return await lt.check({ ...params, mode: undefined }); // default ALL\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["http","bad-request","invalid-parameter","enum","languagetool"],"backgroundTag":"invalid-enum-value","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}