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 "?";
    }
  }

  @NotNull

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Send exactly one of: mode=textLevelOnly, mode=allButTextLevelOnly, or mode=all (lowercase as shown)
  2. Fix casing — matching is case-sensitive via String.equals
  3. Remove the mode parameter entirely to get the default Mode.ALL
  4. 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

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


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