languagetool-org/languagetool · error · SAXException

Only backward references in match elements are possible…

Error message

Only backward references in match elements are possible, tried to specify token ${refNumber}
Line: ${pLocator.getLineNumber()}, column: ${pLocator.getColumnNumber()}.

What it means

XMLRuleHandler.checkRefNumber throws this SAXException when a <match no='N'> reference points to a token at or after the current pattern position. References in match elements may only be backward (to earlier tokens), because the token being matched cannot reference itself or a future token. The error names the offending token number and the XML location.

Solutions

  1. Lower the 'no' value so it refers to an earlier token in the pattern
  2. Move the <match> element after the token it references, or restructure the rule
  3. Re-count token positions (1-based, counting only preceding pattern tokens) after editing the rule

Example fix

// before (first token cannot reference token 2)
<token>foo</token><match no="2"/>
// after
<token>foo</token><match no="1"/>
Defensive patterns

Strategy: validation

Validate before calling

// While building rules programmatically, track current token position
int currentPosition = /* index of token being defined */;
int refNumber = parseNo(matchElement);
if (refNumber > currentPosition) {
  throw new IllegalArgumentException("Forward reference no=" + refNumber + " at token " + currentPosition);
}

Type guard

boolean isBackwardReference(int refNumber, int tokensDefinedSoFar) {
  return refNumber >= 1 && refNumber <= tokensDefinedSoFar;
}

Try / catch

try {
  loader.getRules(is, filename);
} catch (SAXException e) {
  if (e.getMessage().startsWith("Only backward references")) {
    log.error("Renumber match references; they may only point to earlier tokens", e);
  } else throw e;
}

Prevention

When it happens

Trigger: Parsing rule XML where the 'no' attribute exceeds patternTokens.size() at parse time, e.g. <match no="2"/> placed as the first token, or referencing a token not yet defined in the pattern.

Common situations: Reordering tokens after writing matches and forgetting to renumber references; authors trying forward references; rules copied with offsets that no longer line up.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at languagetool-core/src/main/java/org/languagetool/rules/patterns/XMLRuleHandler.java:433

      elements.append(refNumber);
    }
  }

  private void checkNumber(Attributes attrs) throws SAXException {
    if (StringTools.isEmpty(attrs.getValue("no"))) {
      throw new SAXException("References cannot be empty: " + "\n Line: "
          + pLocator.getLineNumber() + ", column: "
          + pLocator.getColumnNumber() + ".");
    } else if (Integer.parseInt(attrs.getValue("no")) < 1 && regex.length() == 0) {
      throw new SAXException("References must be larger than 0: "
          + attrs.getValue("no") + "\n Line: " + pLocator.getLineNumber()
          + ", column: " + pLocator.getColumnNumber() + ".");
    }
  }

  private void checkRefNumber(int refNumber) throws SAXException {
    if (refNumber > patternTokens.size()) {
      throw new SAXException("Only backward references in match elements are possible, tried to specify token "
          + refNumber + "\n" + "Line: " + pLocator.getLineNumber()
          + ", column: " + pLocator.getColumnNumber() + ".");
    }
  }

  protected void setExceptions(Attributes attrs) {
    inException = true;
    exceptions = new StringBuilder();
    resetException();

    exceptionStringNegation = YES.equals(attrs.getValue(NEGATE));
    exceptionValidNext = "next".equals(attrs.getValue(SCOPE));
    exceptionValidPrev = "previous".equals(attrs.getValue(SCOPE));
    exceptionStringInflected = YES.equals(attrs.getValue(INFLECTED));

    if (attrs.getValue(POSTAG) != null) {
      exceptionPosToken = StringInterner.intern(attrs.getValue(POSTAG));
      exceptionPosRegExp = YES.equals(attrs.getValue(POSTAG_REGEXP));

View on GitHub (pinned to 2e990059ce)