{"record":{"id":"0d8f93504be35187","repo":"languagetool-org/languagetool","slug":"confusion-set-must-be-of-size-2","errorCode":null,"errorMessage":"Confusion set must be of size 2: ","messagePattern":"Confusion set must be of size 2: ","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"languagetool-core/src/main/java/org/languagetool/rules/ngrams/ConfusionProbabilityRule.java","lineNumber":297,"sourceCode":"  public void setConfusionPair(ConfusionPair pair) {\n    wordToPairs.clear();\n    for (ConfusionString word : pair.getTerms()) {\n      wordToPairs.put(word.getString(), Collections.singletonList(pair));\n    }\n  }\n\n  /**\n   * Returns the ngram level used, typically 3.\n   * @since 3.1\n   */\n  public int getNGrams() {\n    return grams;\n  }\n\n  @Nullable\n  private ConfusionString getBetterAlternativeOrNull(GoogleToken token, List<GoogleToken> tokens, List<ConfusionString> confusionSet, long factor) {\n    if (confusionSet.size() != 2) {\n      throw new RuntimeException(\"Confusion set must be of size 2: \" + confusionSet);\n    }\n    ConfusionString other = getAlternativeTerm(confusionSet, token);\n    return getBetterAlternativeOrNull(token, tokens, other, factor);\n  }\n\n  private ConfusionString getAlternativeTerm(List<ConfusionString> confusionSet, GoogleToken token) {\n    for (ConfusionString s : confusionSet) {\n      if (!s.getString().equals(token.token)) {\n        return s;\n      }\n    }\n    throw new RuntimeException(\"No alternative found for: \" + token);\n  }\n\n  private ConfusionString getConfusionString(List<ConfusionString> confusionSet, GoogleToken token) {\n    for (ConfusionString s : confusionSet) {\n      if (s.getString().equalsIgnoreCase(token.token)) {\n        return s;","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/rules/ngrams/ConfusionProbabilityRule.java#L279-L315","documentation":"getBetterAlternativeOrNull expects each confusion set to contain exactly two candidate words (the word and its alternative). This RuntimeException is an internal invariant check that fires when a confusion set loaded from the confusion data has a size other than 2.","triggerScenarios":"A confusion set loaded from the language's confusion data resource contains one or more than two entries, then the rule tries to compute the better alternative during rule matching.","commonSituations":"Malformed or hand-edited confusion-set data files; a data update changed the set format; case-related duplicates collapsed into one entry.","solutions":["Inspect the confusion set printed in the message and find its source entry in the language's confusion data.","Ensure each confusion set has exactly two entries.","Re-generate or re-download the confusion data for the language if it was modified."],"exampleFix":"// before (confusion data: 1 entry)\ntheir\n// after (confusion data: 2 entries)\ntheir\nthere","handlingStrategy":"validation","validationCode":"// validate confusion data sets are pairs before running rules\nconfusionSets.forEach((word, set) -> {\n    if (set.size() != 2) throw new IllegalStateException(\"Confusion set must be size 2: \" + word + \" -> \" + set);\n});","typeGuard":null,"tryCatchPattern":"try { rule.match(...); } catch (RuntimeException e) { if (e.getMessage().startsWith(\"Confusion set must be of size 2\")) { log.error(\"Corrupt confusion data: \" + e.getMessage()); } throw e; }","preventionTips":["Validate confusion data files in CI","Re-download data after upstream updates rather than hand-editing","Keep pairs of distinct words in every set"],"tags":["java","ngrams","invariant-violation","languagetool"],"backgroundTag":"internal-invariant-violation","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"}