languagetool-org/languagetool · error · RuntimeException

Expected at least '1grams', '2grams', and '3grams' sub direc

Error message

Expected at least '1grams', '2grams', and '3grams' sub directories but only got ${dirs} in ${topIndexDir.getAbsolutePath()}

What it means

LuceneSingleIndexLanguageModel requires all three of '1grams', '2grams', '3grams' subdirectories. If validateDirectory finds some but fewer than three, it throws RuntimeException listing which ones were found and where.

Source

Thrown at languagetool-core/src/main/java/org/languagetool/languagemodel/LuceneSingleIndexLanguageModel.java:72

   */
  public static void validateDirectory(File topIndexDir) {
    if (!topIndexDir.exists() || !topIndexDir.isDirectory()) {
      throw new RuntimeException("Not found or is not a directory:\n" +
              topIndexDir + "\n" +
              "As ngram directory, please select the directory that has a subdirectory like 'en'\n" +
              "(or whatever language code you're using).");
    }
    List<String> dirs = new ArrayList<>();
    for (String name : topIndexDir.list()) {
      if (name.matches("[123]grams")) {
        dirs.add(name);
      }
    }
    if (dirs.isEmpty()) {
      throw new RuntimeException("Directory must contain at least '1grams', '2grams', and '3grams': " + topIndexDir.getAbsolutePath());
    }
    if (dirs.size() < 3) {
      throw new RuntimeException("Expected at least '1grams', '2grams', and '3grams' sub directories but only got " + dirs + " in " + topIndexDir.getAbsolutePath());
    }
  }
  
  /**
   * Only used internally. 
   * @since 3.2 
   */
  @Experimental
  public static void clearCaches() {
    dirToSearcherMap.clear();
  }

  /**
   * @param topIndexDir a directory which contains at least another sub directory called {@code 3grams},
   *                    which is a Lucene index with ngram occurrences as created by
   *                    {@code org.languagetool.dev.FrequencyIndexCreator}.
   */
  public LuceneSingleIndexLanguageModel(File topIndexDir)  {

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Re-download/re-extract the full ngram index so 1grams, 2grams and 3grams all exist
  2. Move/copy the missing ngrams subdirectory into the index dir
  3. Use a constructor/variant that matches the available data

Example fix

// before
ls /data/en/  ->  1grams 2grams   // 3grams missing
// after
unzip en-ngrams.zip -d /data/en/  # restores 1grams 2grams 3grams
Defensive patterns

Strategy: validation

Validate before calling

Set<String> found = new HashSet<>();
for (File f : ngramDir.listFiles(File::isDirectory)) if (f.getName().matches("[123]grams")) found.add(f.getName());
if (found.size() < 3) throw new IllegalStateException("Missing ngram dirs, only got: " + found);

Try / catch

try { model = new LuceneSingleIndexLanguageModel(ngramDir); } catch (RuntimeException e) { if (e.getMessage().startsWith("Expected at least")) { reDownloadNgramIndex(ngramDir); model = new LuceneSingleIndexLanguageModel(ngramDir); } else throw e; }

Prevention

When it happens

Trigger: Constructing the model against an index directory containing only a subset of the required ngram subdirectories (e.g. only 1grams and 2grams present).

Common situations: Partial or corrupted download/extraction of the ngram index; manually deleting a large ngram folder to save space; a different maxNgram index layout passed to the wrong constructor.

Related errors


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