languagetool-org/languagetool · error · IllegalArgumentException

plainTextPosition must be >= 0: ${plainTextPosition}

Error message

plainTextPosition must be >= 0: ${plainTextPosition}

What it means

AnnotatedText.getOriginalTextPositionFor() maps a position in the plain text (markup stripped) back to the original document position. Negative positions are meaningless, so it throws immediately. This is an input-validation error in the caller's offset arithmetic.

Source

Thrown at languagetool-core/src/main/java/org/languagetool/markup/AnnotatedText.java:111

    StringBuilder sb = new StringBuilder();
    for (TextPart part : parts) {
      if (part.getType() != TextPart.Type.FAKE_CONTENT) {
        sb.append(part.getPart());
      }
    }
    return sb.toString();
  }

  /**
   * Internally used by LanguageTool to adjust error positions to point to the
   * original location with markup, even though markup was ignored during text checking.
   * @param plainTextPosition the position in the plain text (no markup) that was checked
   * @param isToPos the from/to position needed
   * @return an adjusted position of the same location in the text with markup
   */
  public int getOriginalTextPositionFor(int plainTextPosition, boolean isToPos) {
    if (plainTextPosition < 0) {
      throw new IllegalArgumentException("plainTextPosition must be >= 0: " + plainTextPosition);
    }
    if (mapping.isEmpty()) {
      return 0;
    }
    int minDiff = Integer.MAX_VALUE;
    MappingValue bestMatch = null;
    // algorithm: find the closest lower position
    for (Map.Entry<Integer, MappingValue> entry : mapping.entrySet()) {
      int maybeClosePosition = entry.getKey();
      if (plainTextPosition < maybeClosePosition) {
        int diff = maybeClosePosition - plainTextPosition;
        if (diff > 0 && diff < minDiff) {
          bestMatch = entry.getValue();
          minDiff = diff;
        }
      }
    }
    if (bestMatch == null) {

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Fix the rule producing the negative offset (empty match or lookaround shifting start before position 0).
  2. Clamp offsets: Math.max(0, plainTextPosition) before calling the mapping API.
  3. Validate that fromPos/toPos of the rule match are >= 0 before adjusting positions.
  4. Update/upgrade the rule definition if it comes from a shipped grammar/wording rule set.

Example fix

// before
int orig = text.getOriginalTextPositionFor(match.getOffset(), false);
// after
int plainPos = Math.max(0, match.getOffset());
int orig = text.getOriginalTextPositionFor(plainPos, false);
Defensive patterns

Strategy: validation

Validate before calling

if (plainTextPosition < 0) plainTextPosition = 0; // or reject the rule match
int orig = text.getOriginalTextPositionFor(plainTextPosition, isToPos);

Prevention

When it happens

Trigger: Calling getOriginalTextPositionFor() with a negative plainTextPosition, typically from rule-match offset adjustment (adjustRuleMatchPos/adjustOffset/getRangesFromSentences) when a rule produced an erroneous negative offset (e.g. regex lookarounds or matching the empty string at position 0 shifting offsets).

Common situations: A regex rule matching an empty string or using lookbehind/lookahead that yields match start < 0 in the plain-text view; bugs in custom rules' offset calculations.

Related errors


AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06). Data as JSON: /api/errors/841f63d6eec0fda6. Report an issue: GitHub.