Calling XSLT from Java, .NET and Python

Compile the stylesheet once, pass parameters in, and treat any stylesheet that came from outside as executable code.

The three APIs

import javax.xml.transform.*;
import javax.xml.transform.stream.*;

public class Run {
  // Compile once at startup; a Templates object is not thread-safe, so use a pool
  static final Templates TEMPLATE = load();

  static Templates load() {
    try {
      TransformerFactory f = TransformerFactory.newInstance();
      // Route to Saxon when you need XSLT 2.0 or 3.0
      // f = new net.sf.saxon.TransformerFactoryImpl();
      f.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
      return f.newTemplates(new StreamSource(new java.io.File("transform.xsl")));
    } catch (TransformerException e) { throw new IllegalStateException(e); }
  }

  public static void main(String[] args) throws Exception {
    Transformer t = TEMPLATE.newTransformer();
    t.setParameter("locale", "en-GB");
    t.transform(new StreamSource(new java.io.File("input.xml")),
                new StreamResult(new java.io.File("out.html")));
  }
}
using System.Xml;
using System.Xml.Xsl;

var xslt = new XslCompiledTransform();
xslt.Load("transform.xsl", new XsltSettings(enableDocumentFunction: false,
                                           enableScript: false),
          resolver: null);

var args = new XsltArgumentList();
args.AddParam("locale", "", "en-GB");

using var reader = XmlReader.Create("input.xml");
using var writer = XmlWriter.Create("out.html",
    new XmlWriterSettings { Indent = false });
xslt.Transform(reader, args, writer);
from lxml import etree

# Compile once, then call it many times
transform = etree.XSLT(etree.parse("transform.xsl"))

for path in ["a.xml", "b.xml"]:
    result = transform(etree.parse(path), locale="en-GB")
    if not result:                       # an empty tree means the transform failed
        raise RuntimeError(str(transform.error_log))
    open(path.replace(".xml", ".html"), "wb").write(bytes(result))
ConcernJava.NETPython lxml
Engine version1.0 by default, Saxon optional1.0 only1.0 via libxslt
Compiled object reuseTemplatesXslCompiledTransformetree.XSLT
Thread safetyCreate a Transformer per threadNot documented as safe; synchroniseNot thread-safe, one per thread
ParameterssetParameterXsltArgumentListKeyword arguments
ExtensionsJava calls via Xalan/Saxonmsxsl:scriptlxml extension functions

Hardening an untrusted stylesheet

  • A stylesheet can read files: document() in the standard set is an arbitrary-file-read primitive. Disable it when the stylesheet is not yours.
  • Extension functions can execute code. On .NET disable enableScript; on Java disable external function resolution.
  • Set secure processing, which caps entity expansion and disallows access to external resources.
  • Set a transform timeout: a pathological stylesheet can be quadratic, and a shared worker pool will not protect you.
  • Cache compiled templates keyed by file path plus modification time, and cap the cache.
from lxml import etree

# A parser that refuses to fetch or expand anything, for untrusted XML
parser = etree.XMLParser(resolve_entities=False, no_network=True,
                         load_dtd=False, huge_tree=False)

doc = etree.parse("uploaded.xml", parser)
transform = etree.XSLT(etree.parse("trusted.xsl"))
result = transform(doc)
⚠️
Compiling a stylesheet on every request is the most common performance mistake in XSLT-backed services. Compilation costs milliseconds to tens of milliseconds and is pure overhead once the file stops changing — compile at startup and reuse the Templates object.

FAQ

How do I use XSLT 2.0 from Java?
Put saxon-he on the classpath and set the transformer factory explicitly, either with the system property javax.xml.transform.TransformerFactory or by instantiating Saxon's factory directly.
Why is the output empty in lxml?
lxml returns an empty result rather than raising on transform failure. Check transform.error_log, which holds the message and the line number in the stylesheet.

XSLT versions, engines and where transformations run Debugging and testing stylesheets

Last refreshed 2026-09-18.