stanfordnlp/CoreNLP · error

Got a close tag </ > which does not match any open tag

Error message

Got a close tag </${tag.name}> which does not match any open tag

What it means

CleanXmlAnnotator strips XML/SGML-style markup from tokenized text while tracking an enclosing-tag stack. When it encounters a close tag </name> while the open-tag stack is empty, there is nothing to match it against; if allowFlawedXml is false it throws IllegalArgumentException, otherwise it logs this warning and skips the stray close tag.

Solutions

  1. Set the clean.xmlallowFlawedXml option (clean.xmlallowflawedxml=true) to tolerate unmatched tags, turning the exception into a logged warning.
  2. Fix the upstream text so fragments include their opening tags, or escape/remove stray '<'/'>' before tokenization.
  3. Validate/sanitize the XML markup before running the annotator (e.g., with an XML parser or regex repair pass).
  4. Wrap the annotation step in try/catch for IllegalArgumentException if the input is untrusted and skip failing documents.

Example fix

// before
props.setProperty("clean.xml", "true");
// throws on '</b>' with no opener

// after
props.setProperty("clean.xml", "true");
props.setProperty("clean.xmlallowflawedxml", "true"); // stray close tags are logged and skipped
Defensive patterns

Strategy: try-catch

Validate before calling

// Quick sanity check for stray close tags before annotation
int open = 0;
for (String tok : tokens) {
  if (tok.matches("<[^/][^>]*>")) open++;
  if (tok.matches("</[^>]+>") && --open < 0) throw new IllegalArgumentException("Stray close tag: " + tok);
}

Try / catch

// Tolerate malformed markup for untrusted input
try {
  pipeline.annotate(doc);
} catch (IllegalArgumentException e) {
  if (e.getMessage() != null && e.getMessage().contains("close tag")) {
    // re-run with clean.xmlallowflawedxml=true or skip document
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling CleanXmlAnnotator.process on a CoreMap whose tokens contain an XML close tag with no preceding matching open tag — e.g., text fragment starting mid-document like '</p>' or an unescaped '<' token that the tokenizer split into a tag-like token.

Common situations: Annotating document fragments/excerpts cut from larger XML files; scraping pipeline output with broken markup; tokenizers treating stray '<' '>' characters as XML tokens in plain text.

Related errors


AI-assisted analysis of stanfordnlp/CoreNLP@1b7edd19c4 (2026-09-10). Data as JSON: /api/errors/f0da001c367c54a2. Report an issue: GitHub.

Appendix: source

Thrown at src/edu/stanford/nlp/pipeline/CleanXmlAnnotator.java:860

          markSingleSentence = true;
        }
      }

      if (xmlTagMatcher == null)
        continue;

      if (tag.isSingleTag) {
        continue;
      }
      // at this point, we can't reuse the "currentTagSet" vector
      // any more, since the current tag set has changed
      currentTagSet = null;
      if (tag.isEndTag) {
        while (true) {
          if (enclosingTags.isEmpty()) {
            String mesg = "Got a close tag </" + tag.name + "> which does not match any open tag";
            if (allowFlawedXml) {
              log.warn(mesg);
              break;
            } else {
              throw new IllegalArgumentException(mesg);
            }
          }
          String lastTag = enclosingTags.pop();
          if (xmlTagMatcher.matcher(lastTag).matches()) {
            matchDepth--;
          }
          if (lastTag.equals(tag.name)) {
            break;
          }
          String mesg = "Mismatched tags: </" + tag.name + "> closed a <" + lastTag + "> tag.";
          if ( ! allowFlawedXml) {
            throw new IllegalArgumentException(mesg);
          } else {
            log.warn(mesg);
          }

View on GitHub (pinned to 1b7edd19c4)