{"record":{"id":"5051f0cba546957d","repo":"languagetool-org/languagetool","slug":"minoccurrence-must-be-0-minoccurrence","errorCode":null,"errorMessage":"minOccurrence must be >= 0: ${minOccurrence}","messagePattern":"minOccurrence must be >= 0: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"languagetool-core/src/main/java/org/languagetool/rules/patterns/PatternTokenBuilder.java","lineNumber":85,"sourceCode":"  public PatternTokenBuilder csTokenRegex(String token) {\n    this.token = Objects.requireNonNull(token);\n    regexp = true;\n    caseSensitive = true;\n    return this;\n  }\n\n  public PatternTokenBuilder pos(String posTag) {\n    return pos(posTag, false);\n  }\n\n  public PatternTokenBuilder posRegex(String posTag) {\n    return pos(posTag, true);\n  }\n\n  /** @since 4.9 */\n  public PatternTokenBuilder min(int val) {\n    if (val < 0) {\n      throw new IllegalArgumentException(\"minOccurrence must be >= 0: \" + minOccurrence);\n    }\n    minOccurrence = val;\n    return this;\n  }\n\n  /** @since 4.9 */\n  public PatternTokenBuilder max(int val) {\n    maxOccurrence = val;\n    return this;\n  }\n\n  /**\n   * Corresponds to {@code <marker>...</marker>} in XML. Note that there\n   * can be more tokens with a mark, but then must all be adjacent.\n   * @since 4.6\n   */\n  public PatternTokenBuilder mark(boolean isMarked) {\n    this.marker = isMarked;","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/rules/patterns/PatternTokenBuilder.java#L67-L103","documentation":"PatternTokenBuilder.min(int) rejects negative minimum-occurrence values, throwing IllegalArgumentException when val < 0. Note the message mistakenly prints the field minOccurrence (the previous value, possibly null) instead of the parameter val — the offending input is the negative val you passed. Valid minimums are 0 or 1 once passed to the token.","triggerScenarios":"Calling new PatternTokenBuilder().min(-1) or any negative value; builders computing min counts from arithmetic that can go negative (e.g. max - n).","commonSituations":"Quantifier arithmetic producing negative minimums; off-by-one in code that decrements counts; passing a sentinel -1 to mean 'unlimited' (which belongs in min()/max() semantics differently here).","solutions":["Pass a non-negative value; use 0 for an optional token via min(0) combined with an appropriate max.","Clamp before calling: Math.max(0, computedMin).","Fix quantifier arithmetic so the minimum cannot go below 0.","If you intended unlimited, do not use min(); configure max(-1) and set optionality separately."],"exampleFix":"// before\nbuilder.min(count - 2); // can be negative\n// after\nbuilder.min(Math.max(0, count - 2));","handlingStrategy":"validation","validationCode":"int safeMin = Math.max(0, computedMin);\nbuilder.min(safeMin);","typeGuard":"boolean isNonNegative(int i) { return i >= 0; }","tryCatchPattern":"try {\n  builder.min(val);\n} catch (IllegalArgumentException e) {\n  builder.min(0); // log the negative computed value\n}","preventionTips":["Clamp computed minimums with Math.max(0, ...) before calling min().","Remember the error message shows the stale field, not your input; debug with the parameter value.","Use min(0) for optional tokens rather than negative sentinels.","Unit-test builder chains with edge-case counts."],"tags":["validation","builder","range-check","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"}