languagetool-org/languagetool · error · BadRequestException

Invalid 'preferredVariants', no such language/variant found:

Error message

Invalid 'preferredVariants', no such language/variant found: '{preferredVariant}'

What it means

The preferredVariants entry has a valid 'xx-YY' shape but LanguageTool has no registered language/variant matching it. parseLanguage() returns null for unknown combinations, so the server throws this BadRequestException. The language part must match the request's short code for this branch to apply.

Source

Thrown at languagetool-server/src/main/java/org/languagetool/server/TextChecker.java:1023

    } else {
      lang = detected.getDetectedLanguage();
    }
    //String mode;
    //long t1 = System.nanoTime();
    //long t2 = System.nanoTime();
    //float runTime = (t2-t1)/1000.0f/1000.0f;
    //System.out.printf(Locale.ENGLISH, "detected " + detected + " using " + mode + " in %.2fms for %d chars\n", runTime, text.length());
    
    if (preferredVariants.size() > 0) {
      for (String preferredVariant : preferredVariants) {
        if (!preferredVariant.contains("-")) {
          throw new BadRequestException("Invalid format for 'preferredVariants', expected a dash as in 'en-GB': '" + preferredVariant + "'");
        }
        String preferredVariantLang = preferredVariant.split("-")[0];
        if (preferredVariantLang.equals(lang.getShortCode())) {
          lang = parseLanguage(preferredVariant);
          if (lang == null) {
            throw new BadRequestException("Invalid 'preferredVariants', no such language/variant found: '" + preferredVariant + "'");
          }
        }
      }
    } else {
      if (lang.getDefaultLanguageVariant() != null) {
        lang = lang.getDefaultLanguageVariant();
      }
    }
    return new DetectedLanguage(null, lang, detected != null ? detected.getDetectionConfidence() : 0f,
      detected != null ? detected.getDetectionSource() : null);
  }

  static class QueryParams {
    final List<Language> altLanguages;
    final List<String> enabledRules;
    final List<String> disabledRules;
    final List<CategoryId> enabledCategories;
    final List<CategoryId> disabledCategories;

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Verify the exact variant exists, e.g. use en-GB not en-UK
  2. List available variants via the /v2/languages endpoint and pick from it
  3. Validate user input against the supported language list before sending
  4. Check the variant's language prefix matches your 'language' parameter

Example fix

// before
params.put("preferredVariants", "en-UK");
// after
params.put("preferredVariants", "en-GB");
Defensive patterns

Strategy: validation

Validate before calling

const supported = await fetch(`${LT_URL}/v2/languages`).then(r => r.json());
const valid = supported.some(l => `${l.code}-${l.code.toUpperCase()}` === variant || l.name === variant);

Type guard

const isKnownVariant = (v, list) => list.includes(v);

Try / catch

try { const res = await check(params); } catch (e) { if (e.status === 400 && /no such language\/variant/.test(e.message)) { /* fall back to default variant */ } }

Prevention

When it happens

Trigger: Calling /v2/check with e.g. preferredVariants=en-XX, xx-YY or zz-FAKE, or a variant that exists for another language than the current lang short code path resolves; typos like 'en-GBB'.

Common situations: Misspelled variant codes; inventing variants that don't exist (en-UK — the correct code is en-GB); stale hardcoded lists after LanguageTool upgrades; user-supplied locale strings passed through unvalidated.

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/3535cf2b74af79b5. Report an issue: GitHub.