stanfordnlp/CoreNLP · error · RuntimeException
TIMEX3 should only contain text
Error message
TIMEX3 should only contain text ${child} What it means
HeidelTimeAnnotator's toTimexCoreMaps parses HeidelTime's XML output; each TIMEX3 element produced by HeidelTime is expected to wrap exactly one text node. When a TIMEX3 element has zero or more than one child nodes (e.g. nested elements or split text), the annotator throws this RuntimeException because the offset/token mapping logic cannot handle that shape.
Solutions
- Verify the external heideltime.jar version matches what this CoreNLP version expects; replace with the bundled/supported version
- Inspect the HeidelTime XML output (enable debug printing) to find the offending TIMEX3 element and check why it has multiple children
- Pre-normalize the input text so HeidelTime does not emit nested tags (e.g. strip XML/HTML from the document before annotation)
- Patch toTimexCoreMaps to flatten multi-child TIMEX3 elements by iterating child text nodes instead of requiring exactly one
Example fix
// before: hard failure on non-flat TIMEX3
if (child.getChildNodes().getLength() != 1) {
throw new RuntimeException("TIMEX3 should only contain text " + child);
}
// after: flatten multiple text children
StringBuilder sb = new StringBuilder();
NodeList kids = child.getChildNodes();
for (int j = 0; j < kids.getLength(); j++) {
if (kids.item(j) instanceof Text) sb.append(kids.item(j).getTextContent());
}
String timexText = sb.toString(); Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure input to HeidelTimeAnnotator is plain, tokenizable text
String docText = annotation.get(CoreAnnotations.TextAnnotation.class);
if (docText == null || docText.trim().isEmpty()) {
throw new IllegalArgumentException("document text is empty");
}
if (docText.contains("<TIMEX3")) {
throw new IllegalArgumentException("input already contains TIMEX3 markup; pass plain text");
} Type guard
static boolean isFlatTimex3(Element el) {
return "TIMEX3".equals(el.getNodeName()) && el.getChildNodes().getLength() == 1
&& el.getChildNodes().item(0) instanceof Text;
} Try / catch
try {
pipeline.annotate(annotation);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().startsWith("TIMEX3 should only contain text")) {
// fall back: skip time annotation or re-run with sanitized input
logger.warn("HeidelTime produced non-flat TIMEX3; skipping doc " + annotation.get(CoreAnnotations.DocIDAnnotation.class));
} else {
throw e;
}
} Prevention
- Use the heideltime.jar version bundled/tested with your CoreNLP release
- Strip XML/HTML markup from documents before running HeidelTime
- Test new corpora for odd date expressions that produce nested TIMEX3 output
- Wrap annotator calls so one bad document does not kill the whole pipeline
When it happens
Trigger: Running the HeidelTimeAnnotator (or a StanfordCoreNLP pipeline containing 'heideltime') on a document whose HeidelTime output contains a TIMEX3 element with nested/multiple children — typically when HeidelTime tags a span that includes markup or the external heideltime.jar version emits non-flat TIMEX3 elements.
Common situations: Using a mismatched or customized heideltime.jar whose output format differs from what this annotator expects; documents with unusual date expressions causing HeidelTime to emit nested TIMEX3 tags; passing pre-annotated XML rather than plain text.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- unexpected element
- unexpected content
- Unknown minimizer
- Unknown clique: " + clique
- Bad number put into wordToNumber. Word is: \"" + input +…
AI-assisted analysis of stanfordnlp/CoreNLP@1b7edd19c4 (2026-09-10).
Data as JSON: /api/errors/67a5c5696ac742a3.
Report an issue: GitHub.
Appendix: source
Thrown at src/edu/stanford/nlp/time/HeidelTimeAnnotator.java:218
int charEnd = token.get(CoreAnnotations.CharacterOffsetEndAnnotation.class);
beginMap.put(charBegin,tokBegin);
endMap.put(charEnd,tokEnd);
}
}
List<CoreMap> timexMaps = new ArrayList<>();
int offset = 0;
NodeList docNodes = docElem.getChildNodes();
for (int i = 0; i < docNodes.getLength(); i++) {
Node content = docNodes.item(i);
if (content instanceof Text) {
Text text = (Text)content;
offset += text.getWholeText().length();
} else if (content instanceof Element) {
Element child = (Element)content;
if (child.getNodeName().equals("TIMEX3")) {
Timex timex = new Timex(child);
if (child.getChildNodes().getLength() != 1) {
throw new RuntimeException("TIMEX3 should only contain text " + child);
}
String timexText = child.getTextContent();
CoreMap timexMap = new ArrayCoreMap();
timexMap.set(TimeAnnotations.TimexAnnotation.class, timex);
timexMap.set(CoreAnnotations.TextAnnotation.class, timexText);
int charBegin = offset;
timexMap.set(CoreAnnotations.CharacterOffsetBeginAnnotation.class, offset);
offset += timexText.length();
timexMap.set(CoreAnnotations.CharacterOffsetEndAnnotation.class, offset);
int charEnd = offset;
//(tokens)
if(haveTokenOffsets){
Integer tokBegin = beginMap.get(charBegin);
int searchStep = 1; //if no exact match, search around the character offset
while(tokBegin == null){
tokBegin = beginMap.get(charBegin - searchStep);
if(tokBegin == null){
tokBegin = beginMap.get(charBegin + searchStep);View on GitHub (pinned to 1b7edd19c4)