{"record":{"id":"1d190979f01db4bd","repo":"languagetool-org/languagetool","slug":"maxoccurrences-may-not-be-0","errorCode":null,"errorMessage":"maxOccurrences may not be 0","messagePattern":"maxOccurrences may not be 0","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"languagetool-core/src/main/java/org/languagetool/rules/patterns/PatternToken.java","lineNumber":426,"sourceCode":"\n  /**\n   * The minimum number of times this element may occur.\n   * @param i currently only {@code 0} and {@code 1} are supported\n   */\n  public void setMinOccurrence(int i) {\n    if (i != 0 && i != 1) {\n      throw new IllegalArgumentException(\"minOccurrences must be 0 or 1: \" + i);\n    }\n    setFlag(MAY_BE_OMITTED_MASK, i == 0);\n  }\n\n  /**\n   * The maximum number of times this element may occur.\n   * @param i a number &gt;= 1 or {@code -1} for unlimited occurrences\n   */\n  public void setMaxOccurrence(int i) {\n    if (i == 0) {\n      throw new IllegalArgumentException(\"maxOccurrences may not be 0\");\n    }\n    if (i < -1 || i > Byte.MAX_VALUE) {\n      throw new IllegalArgumentException(\"maxOccurrences should be between -1 and \" + Byte.MAX_VALUE + \" but was: \" + i);\n    }\n    maxOccurrence = (byte) i;\n  }\n\n  /**\n   * Checks if the element has an exception for a previous token.\n   * @return True if the element has a previous token matching exception.\n   */\n  public boolean hasPreviousException() {\n    return rareFields != null && rareFields.previousExceptions.length > 0;\n  }\n\n  /**\n   * Checks if the element has an exception for a next scope.\n   * (only used for testing)","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/rules/patterns/PatternToken.java#L408-L444","documentation":"PatternToken.setMaxOccurrence(int) rejects 0 outright: a token element must occur at least once or be declared optional via min occurrence, never 'zero times max'. Any call with i == 0 throws IllegalArgumentException before the range check.","triggerScenarios":"Calling setMaxOccurrence(0), directly or via build() when rule XML specifies maxOccurs=\"0\" or a builder passes 0 (see testZeroMinOccurrences2 / testZeroMinTwoMaxOccurrences).","commonSituations":"Authors who intend an optional token writing max=\"0\" instead of min=\"0\"; auto-generated rules from quantifier math where max computes to 0; confusion with regex {0} semantics.","solutions":["Use setMinOccurrence(0) for an optional token instead of setMaxOccurrence(0).","For truly optional repeats, call setMinOccurrence(0) plus setMaxOccurrence(n) (or -1 for unlimited).","Fix rule XML: replace maxOccurs=\"0\" with minOccurs=\"0\" and a sensible max.","Add a pre-call check: if (max == 0) treat as optional-token config instead."],"exampleFix":"// before\npToken.setMaxOccurrence(0);\n// after\npToken.setMinOccurrence(0);\npToken.setMaxOccurrence(-1); // optional, unlimited","handlingStrategy":"validation","validationCode":"if (maxOccurrence == 0) {\n  pToken.setMinOccurrence(0); // optional token instead\n} else {\n  pToken.setMaxOccurrence(maxOccurrence);\n}","typeGuard":"boolean isValidMax(int i) { return i != 0; }","tryCatchPattern":"try {\n  token.setMaxOccurrence(max);\n} catch (IllegalArgumentException e) {\n  token.setMinOccurrence(0); // interpret max=0 as optional\n}","preventionTips":["Never encode optionality with max=0; use setMinOccurrence(0).","Sanitize quantifier math so max cannot compute to 0.","Fix rule XML maxOccurs=\"0\" to minOccurs=\"0\" plus a valid max.","Add unit tests for optional-token rules."],"tags":["validation","quantifier","java"],"backgroundTag":"invalid-argument-value","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"}