{"record":{"id":"596058b7462516e0","repo":"languagetool-org/languagetool","slug":"your-reference-number-refnumber-is-bigger-than-596058","errorCode":null,"errorMessage":"Your reference number ${refNumber} is bigger than number of matching tokens: ${patternTokens.length}","messagePattern":"Your reference number (.+?) is bigger than number of matching tokens: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"languagetool-core/src/main/java/org/languagetool/rules/patterns/RuleFilterEvaluator.java","lineNumber":69,"sourceCode":"   */\n  public Map<String,String> getResolvedArguments(String filterArgs, AnalyzedTokenReadings[] patternTokens, int patternTokenPos, List<Integer> tokenPositions) {\n    Map<String,String> result = new HashMap<>();\n    String[] arguments = WHITESPACE.split(filterArgs);\n    for (String arg : arguments) {\n      int delimPos = arg.indexOf(':');\n      if (delimPos == -1) {\n        throw new RuntimeException(\"Invalid syntax for key/value, expected 'key:value', got: '\" + arg + \"'\");\n      }\n      String key = arg.substring(0, delimPos);\n      String val = arg.substring(delimPos + 1);\n      if (val.startsWith(\"\\\\\")) {\n        int refNumber = Integer.parseInt(val.replace(\"\\\\\", \"\"));\n        if (refNumber > tokenPositions.size()) {\n          throw new RuntimeException(\"Your reference number \" + refNumber + \" is bigger than the number of tokens: \" + tokenPositions.size());\n        }\n        int correctedRef = getSkipCorrectedReference(tokenPositions, refNumber);\n        if (correctedRef >= patternTokens.length) {\n          throw new RuntimeException(\"Your reference number \" + refNumber +\n                  \" is bigger than number of matching tokens: \" + patternTokens.length);\n        }\n        if (result.containsKey(key)) {\n          throw new RuntimeException(\"Duplicate key '\" + key + \"'\");\n        }\n        result.put(key, patternTokens[correctedRef].getToken());\n      } else {\n        result.put(key, val);\n      }\n    }\n    return result;\n  }\n\n  // when there's a 'skip', we need to adapt the reference number\n  private int getSkipCorrectedReference(List<Integer> tokenPositions, int refNumber) {\n    int correctedRef = 0;\n    int i = 0;\n    for (int tokenPosition : tokenPositions) {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/rules/patterns/RuleFilterEvaluator.java#L51-L87","documentation":"Thrown by LanguageTool's RuleFilterEvaluator when a rule filter argument references a matched token by number (e.g. '1') and that skip-corrected index is beyond the tokens that actually matched the pattern. In LanguageTool rule XML, filters receive resolved arguments built from back-references into the matched sentence; this check prevents an ArrayIndexOutOfBoundsException when the reference exceeds patternTokens.length after skip correction. It indicates the rule's filter attribute points at a token position that does not exist in the current match.","triggerScenarios":"A <filter class='...' args='no:2 arg:3'/> (or similar) references token positions higher than the number of pattern tokens matched, especially when <marker>/<skip> causes fewer tokens than expected to be captured; getResolvedArguments parses each ref value and validates correctedRef < patternTokens.length.","commonSituations":"Authors of custom disambiguator/rule XML write filter references like 'no:3' in a two-token pattern; rules that rely on skip counting assume more matched tokens than are actually available after skip correction; rule copied from another rule with more tokens.","solutions":["Reduce the reference number in the filter args attribute so it indexes within the number of pattern tokens","Add or adjust pattern tokens/skip so that the referenced position actually exists in the match","Log patternTokens.length vs refNumber (enable debug on RuleFilterEvaluator) to see how many tokens matched","Wrap filter evaluation in try-catch during rule test runs to catch and report the offending rule id"],"exampleFix":"// before (rule XML)\n<filter class=\"org.languagetool.rules.en.MyFilter\" args=\"no:3\"/>\n// after (pattern has only 2 tokens, so reference token 2)\n<filter class=\"org.languagetool.rules.en.MyFilter\" args=\"no:2\"/>","handlingStrategy":"validation","validationCode":"// Count backward-referenced token positions in the pattern before loading\nint patternTokenCount = /* number of <token>/<match> pattern tokens in the rule */;\nint maxRef = parseMaxFilterRef(ruleXml); // highest 'no:N' in filter args\nif (maxRef > patternTokenCount) {\n  throw new IllegalStateException(\"Rule \" + ruleId + \": filter ref \" + maxRef + \" exceeds \" + patternTokenCount + \" tokens\");\n}","typeGuard":"boolean isValidRef(int refNumber, int patternTokenCount) {\n  return refNumber >= 1 && refNumber <= patternTokenCount;\n}","tryCatchPattern":"try {\n  evaluator.getResolvedArguments(filterId, args, matches, patternTokens);\n} catch (RuntimeException e) {\n  if (e.getMessage().contains(\"is bigger than number of matching tokens\")) {\n    log.error(\"Bad filter reference in rule; fix args\", e);\n  } else throw e;\n}","preventionTips":["Keep filter reference numbers within the number of pattern tokens (1-based)","Re-count references after editing skip attributes or reordering tokens","Run LanguageTool rule unit tests in CI for every rule change"],"tags":["languagetool","xml-rules","filter","index-out-of-bounds"],"backgroundTag":"index-out-of-bounds","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"}