languagetool-org/languagetool · error · IllegalArgumentException
code is supposed to be a 2 (or rarely 3) character code (unl
Error message
code is supposed to be a 2 (or rarely 3) character code (unless it uses a format with variant, like xx-YY): ''
What it means
LanguageTool's server supports adding custom 'dynamic' languages at startup via properties like `lang-xx` in the server config. This error is thrown when the code extracted from a `lang-*` property key is not a 2- or 3-character language code (and contains no '-' for variant forms like xx-YY). It fails fast so a misconfigured custom language is caught before the server starts serving.
Source
Thrown at languagetool-server/src/main/java/org/languagetool/server/HTTPServerConfig.java:532
File dir = new File(ngramLangIdentData);
if (!dir.exists() || dir.isDirectory()) {
throw new IllegalArgumentException("ngramLangIdentData does not exist or is a directory (needs to be a ZIP file): " + ngramLangIdentData);
}
setNgramLangIdentData(dir);
}
}
} catch (IOException e) {
throw new RuntimeException("Could not load properties from '" + file + "'", e);
}
}
private void addDynamicLanguages(Properties props) throws IOException {
for (Object keyObj : props.keySet()) {
String key = (String)keyObj;
if (key.startsWith("lang-") && !key.contains("-dictPath")) {
String code = key.substring("lang-".length());
if (!code.contains("-") && code.length() != 2 && code.length() != 3) {
throw new IllegalArgumentException("code is supposed to be a 2 (or rarely 3) character code (unless it uses a format with variant, like xx-YY): '" + code + "'");
}
String nameKey = "lang-" + code;
String name = props.getProperty(nameKey);
String dictPathKey = "lang-" + code + "-dictPath";
String dictPath = props.getProperty(dictPathKey);
if (dictPath == null) {
throw new IllegalArgumentException(dictPathKey + " must be set");
}
File dictPathFile = new File(dictPath);
if (!dictPathFile.exists() || !dictPathFile.isFile()) {
throw new IllegalArgumentException("dictionary file does not exist or is not a file: '" + dictPath + "'");
}
ServerTools.print("Adding dynamic spell checker language " + name + ", code: " + code + ", dictionary: " + dictPath);
Language lang = Languages.addLanguage(name, code, new File(dictPath));
// better fail early in case of misconfiguration, so use the language now:
if (!new File(lang.getCommonWordsPath()).exists()) {
throw new IllegalArgumentException("Common words path not found: '" + lang.getCommonWordsPath() + "'");
}View on GitHub (pinned to 2e990059ce)
Solutions
- Rename the property key so the part after `lang-` is a 2- or 3-character ISO 639-1/639-3 code, e.g. `lang-de=German`
- If you need a regional variant, use the hyphenated form like `lang-de-DE` (contains '-', so length check is skipped)
- Remove the stray `lang-` prefixed key if it was not intended to define a custom language
Example fix
// before lang-french=French // after lang-fr=French
Defensive patterns
Strategy: validation
Validate before calling
for (String key : props.stringPropertyNames()) {
if (key.startsWith("lang-")) {
String code = key.substring("lang-".length());
if (!code.contains("-") && code.length() != 2 && code.length() != 3)
throw new IllegalStateException("Invalid dynamic language code in key: " + key);
}
} Type guard
boolean isValidLangCode(String code) {
return code != null && (code.contains("-") || code.length() == 2 || code.length() == 3);
} Prevention
- Always use ISO 639-1 (2-letter) or 639-3 (3-letter) codes in lang-* property keys
- Use the xx-YY hyphenated form for regional variants
- Lint server config files at deploy time for lang- prefixed keys
When it happens
Trigger: A properties file passed via --config contains a key starting with `lang-` whose suffix (the part after `lang-`) is empty, 1 character, or longer than 3 characters and contains no hyphen, e.g. `lang-french=French` or `lang-=...`.
Common situations: Admins write the language NAME instead of the ISO code in the property key (`lang-german`), typos in the code (`lang-deutch`), or an empty key suffix after `lang-`.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- No rules are active. Please make sure your rule ids (<option
- maxWorkQueueSize must be >= 0:
- Invalid value for maxCheckThreads, must be >= 1:
- Invalid value for maxTextCheckerThreads, must be >= 1:
- Invalid value for textCheckerQueueSize, must be >= 1:
AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06).
Data as JSON: /api/errors/ec009e70bde160b5.
Report an issue: GitHub.