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
- Fix the rule producing the negative offset (empty match or lookaround shifting start before position 0).
- Clamp offsets: Math.max(0, plainTextPosition) before calling the mapping API.
- Validate that fromPos/toPos of the rule match are >= 0 before adjusting positions.
- 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
- Clamp rule-match offsets to >= 0 before mapping
- Fix rules whose regex can match empty strings or shift starts before position 0
- Assert match.getFromPos() >= 0 in custom rule code
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
- Could not map ${plainTextPosition} to original position. isT
- Wrong position in WhitespaceCheckFilter: " + pos + ", must b
- Missing key 'date'
- Expected date in format 'yyyy-mm-dd': '" + dateString + "'
- grams must be between 1 and 5:
AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06).
Data as JSON: /api/errors/841f63d6eec0fda6.
Report an issue: GitHub.