{"record":{"id":"6923ad8cc5928815","repo":"languagetool-org/languagetool","slug":"could-not-parse-xml-xml","errorCode":null,"errorMessage":"Could not parse XML: ${xml}","messagePattern":"Could not parse XML: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"languagetool-wikipedia/src/main/java/org/languagetool/dev/dumpcheck/AfterTheDeadlineChecker.java","lineNumber":113,"sourceCode":"    XPath xPath = XPathFactory.newInstance().newXPath();\n    NodeList errors = (NodeList)xPath.evaluate(\"//error\", document, XPathConstants.NODESET);\n    for (int i = 0; i < errors.getLength(); i++) {\n      Node error = errors.item(i);\n      String string = xPath.evaluate(\"string\", error);\n      String description = xPath.evaluate(\"description\", error);\n      matches.add(description + \": \" + string);\n    }\n    return matches;\n  }\n\n  private Document getDocument(String xml) {\n    try {\n      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n      DocumentBuilder builder = factory.newDocumentBuilder();\n      InputSource inputSource = new InputSource(new StringReader(xml));\n      return builder.parse(inputSource);\n    } catch (Exception e) {\n      throw new RuntimeException(\"Could not parse XML: \" + xml, e);\n    }\n  }\n\n  public static void main(String[] args) throws Exception {\n    if (args.length < 4) {\n      System.out.println(\"Usage: \" + AfterTheDeadlineChecker.class.getSimpleName() + \" <langCode> <atdUrlPrefix> <file...>\");\n      System.out.println(\"   <langCode>      a language code like 'en' for English\");\n      System.out.println(\"   <atdUrlPrefix>  URL prefix of After the Deadline server, like 'http://localhost:1059/checkDocument?data='\");\n      System.out.println(\"   <sentenceLimit> Maximum number of sentences to check, or 0 for no limit\");\n      System.out.println(\"   <file...>       Wikipedia and/or Tatoeba file(s)\");\n      System.exit(1);\n    }\n    Language language = Languages.getLanguageForShortCode(args[0]);\n    String urlPrefix = args[1];\n    int maxSentenceCount = Integer.parseInt(args[2]);\n    List<String> files = Arrays.asList(args).subList(3, args.length);\n    AfterTheDeadlineChecker atdChecker = new AfterTheDeadlineChecker(urlPrefix, maxSentenceCount);\n    atdChecker.run(language, files);","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-wikipedia/src/main/java/org/languagetool/dev/dumpcheck/AfterTheDeadlineChecker.java#L95-L131","documentation":"AfterTheDeadlineChecker wraps any exception thrown while parsing the After the Deadline service's XML response into a RuntimeException with the raw XML appended. It is thrown in getDocument when DocumentBuilder.parse fails, meaning the HTTP response body was not well-formed XML. The raw response is included in the message to aid debugging of malformed or unexpected server output.","triggerScenarios":"The ATD server returns HTML (e.g. an error page, proxy block page, or 404 body) instead of XML; the response is truncated or empty; or the response contains characters invalid in XML (bad encoding).","commonSituations":"Wrong atdUrlPrefix pointing at a non-ATD endpoint or behind a captive proxy; ATD server temporarily down returning an error page; network middleware injecting content; version changes in the ATD API output format.","solutions":["Print/log the raw XML from the exception message to see what was actually returned","Verify the atdUrlPrefix points to a working After the Deadline instance (test the URL in a browser/curl)","Check the ATD server is up and not returning an HTML error page (HTTP status, proxy)","Validate encoding of the response and that it is complete (no truncation)"],"exampleFix":"// before\nDocument doc = checker.getDocument(xml);\n// after\nif (xml == null || xml.trim().isEmpty() || !xml.trim().startsWith(\"<?xml\") && !xml.trim().startsWith(\"<\")) {\n  throw new IllegalStateException(\"ATD returned non-XML response: \" + StringUtils.abbreviate(xml, 200));\n}\nDocument doc = checker.getDocument(xml);","handlingStrategy":"validation","validationCode":"if (xml == null || xml.trim().isEmpty() || !(xml.trim().startsWith(\"<?xml\") || xml.trim().startsWith(\"<\"))) {\n  throw new IllegalStateException(\"ATD did not return XML: \" + StringUtils.abbreviate(xml, 200));\n}","typeGuard":"static boolean looksLikeXml(String s) {\n  return s != null && s.trim().startsWith(\"<\");\n}","tryCatchPattern":"try {\n  Document doc = checker.getDocument(xml);\n} catch (RuntimeException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"Could not parse XML\")) {\n    log.warn(\"ATD response not parseable, skipping\", e);\n  } else throw e;\n}","preventionTips":["Log HTTP status and first bytes of every ATD response","Curl the atdUrlPrefix before batch runs","Guard against proxies/captive portals injecting HTML","Set explicit charset on requests"],"tags":["xml","parsing","http-response"],"backgroundTag":"xml-parse-error","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}