Race Condition on a Shared XML Processor

ID

java.race_condition_xml_processor

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Synchronization

Language

Java

Tags

ASVS50:v15.4.1, CWE:362, NIST.SP.800-53, PCI-DSS:6.5.6

Description

Concurrent use of a shared XML processor that is not thread-safe, without synchronization ('Race Condition').

The rule reports a call made on a field whose type is a non-thread-safe XML processor — javax.xml.xpath.XPath and javax.xml.validation.SchemaFactory — when the call sits outside any synchronized block or method. A local instance, or one guarded by a monitor, is not reported.

Other non-thread-safe shared types have their own rule in the same family: race_condition_format for the JDK formatters (SimpleDateFormat and friends), race_condition_matcher for java.util.regex.Matcher.

Rationale

XPath and SchemaFactory are both expensive to create and both explicitly documented as not thread-safe. That combination is a trap: the natural optimisation is to build one instance and cache it in a field, which is exactly what breaks once two request threads reach it.

Nothing fails loudly. Under concurrency the symptoms are a mangled or empty evaluation result, a schema that validates the wrong document, or an exception thrown from deep inside the implementation — all load-dependent, none reproducible in a single-threaded test.

public class Lookup {

  private final XPath xpath = XPathFactory.newInstance().newXPath();

  public String read(String expression, Document doc) throws Exception {
    return (String) xpath.evaluate(expression, doc);   // shared, unsynchronized
  }
}

Remediation

Prefer creating the processor where it is used — the cost is real but bounded, and correctness comes first:

public String read(String expression, Document doc) throws Exception {
  XPath xpath = XPathFactory.newInstance().newXPath();
  return (String) xpath.evaluate(expression, doc);
}

When the creation cost genuinely matters, give each thread its own instance rather than sharing one:

private static final ThreadLocal<XPath> XPATH =
    ThreadLocal.withInitial(() -> XPathFactory.newInstance().newXPath());

public String read(String expression, Document doc) throws Exception {
  return (String) XPATH.get().evaluate(expression, doc);
}

Compiling the expression once into an XPathExpression does not make it safe to share: XPathExpression is not thread-safe either. Synchronizing every use is correct but serialises the work, so it is the last resort.

© 2026 Xygeni. All rights reserved