{"record":{"id":"36f055a92f2814e5","repo":"languagetool-org/languagetool","slug":"searcher-for-ngram-size-ngramsize-already-exist","errorCode":null,"errorMessage":"Searcher for ngram size ${ngramSize} already exists","messagePattern":"Searcher for ngram size (.+?) already exists","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"languagetool-core/src/main/java/org/languagetool/languagemodel/LuceneSingleIndexLanguageModel.java","lineNumber":116,"sourceCode":"      throw new RuntimeException(\"No directories '1grams' ... '3grams' found in \" + topIndexDir);\n    }\n    maxNgram = Collections.<Integer>max(luceneSearcherMap.keySet());\n  }\n\n  public LuceneSingleIndexLanguageModel(int maxNgram) {\n    this.maxNgram = maxNgram;\n    this.topIndexDir = null;\n  }\n\n  protected void doValidateDirectory(File topIndexDir) {\n    validateDirectory(topIndexDir);\n  }\n\n  private void addIndex(File topIndexDir, int ngramSize) {\n    File indexDir = new File(topIndexDir, ngramSize + \"grams\");\n    if (indexDir.exists() && indexDir.isDirectory()) {\n      if (luceneSearcherMap.containsKey(ngramSize)) {\n        throw new RuntimeException(\"Searcher for ngram size \" + ngramSize + \" already exists\");\n      }\n      luceneSearcherMap.put(ngramSize, getCachedLuceneSearcher(indexDir));\n      indexes.add(indexDir);\n    }\n  }\n\n  @Override\n  public long getCount(List<String> tokens) {\n    if (tokens.size() > maxNgram) {\n      throw new RuntimeException(\"Requested \" + tokens.size() + \"gram but index has only up to \" + maxNgram + \"gram: \" + tokens);\n    }\n    Objects.requireNonNull(tokens);\n    Term term = new Term(\"ngram\", String.join(\" \", tokens));\n    return getCount(term, getLuceneSearcher(tokens.size()));\n  }\n\n  @Override\n  public long getCount(String token1) {","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/languagemodel/LuceneSingleIndexLanguageModel.java#L98-L134","documentation":"The private addIndex() method refuses to register a Lucene searcher for an ngram size that already has one in luceneSearcherMap, throwing RuntimeException. It is an internal-consistency guard against adding the same ngram index twice.","triggerScenarios":"Calling addIndex() twice with the same ngramSize for the same model — only possible via internal/reflection code paths, since addIndex is private and the public constructor calls it once per size 1-4.","commonSituations":"Custom subclassing or reflection-based code that re-invokes addIndex; modifications to the constructor that add the same size twice.","solutions":["Remove the duplicate addIndex call for that ngram size","Check luceneSearcherMap (or a containsKey guard) before adding an index","Use the public constructors instead of manipulating internal state"],"exampleFix":"// before\naddIndex(dir, 3);\naddIndex(dir, 3); // RuntimeException\n// after\nif (!luceneSearcherMap.containsKey(3)) addIndex(dir, 3);","handlingStrategy":"validation","validationCode":"if (searcherMap.containsKey(ngramSize)) return searcherMap.get(ngramSize); // reuse instead of re-adding","typeGuard":null,"tryCatchPattern":"try { addIndex(dir, size); } catch (RuntimeException e) { if (e.getMessage().contains(\"already exists\")) return; throw e; }","preventionTips":["Treat addIndex as idempotent-required: guard with containsKey before adding","Avoid reflection/subclassing into private internals; use public constructors","Keep track of registered ngram sizes in your own setup code"],"tags":["java","internal-guard","duplicate-registration"],"backgroundTag":"invalid-state-transition","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"}