Chapter 68: XSLT On the Server
1. Why XSLT is still used on the server in 2025–2026 (honest answer)
Even though many new projects use JSON + JavaScript templates, XSLT remains very important on servers in specific domains:
| Domain / Use case | Typical volume / importance in 2025–2026 | Main reasons XSLT is chosen over JSON templates |
|---|---|---|
| Mandatory e-Invoicing (GST, PEPPOL, Factur-X, ZUGFeRD, XRechnung…) | Extremely high – tens of millions of documents/month | Legal requirement, schema validation, PDF generation, signed XML |
| Financial messaging (ISO 20022, SEPA, SWIFT, FIXML…) | Very high in banks, payment gateways, treasury | Strict message validation, canonicalization, multi-format output (XML, PDF, CSV) |
| Healthcare (HL7 CDA, FHIR XML export, discharge letters) | High | Structured clinical documents, PDF export, audit trail |
| Publishing / technical documentation (DocBook, DITA, S1000D) | Medium–High | Multi-channel publishing (HTML, PDF, mobile, print), reuse of content |
| Legacy enterprise integration | High | Many ERP → partner → ERP flows still use XML + XSLT |
| Batch report generation | Medium | Large XML → styled PDF / HTML / Excel reports |
| Configuration transformation | Medium | XML config → documentation, UI preview, migration scripts |
Bottom line (2025–2026 reality)
- New web APIs → almost always JSON + React/Vue/Angular
- Server-side enterprise / regulated / B2B / document-heavy domains → XSLT is still very much alive (and often mandatory)
2. Most common server-side XSLT scenarios in practice
| # | Scenario | Typical input | Typical output | Common processors (2025) | Typical language/framework |
|---|---|---|---|---|---|
| 1 | e-Invoice → human-readable + PDF | UBL/GST XML | HTML + PDF (via XSL-FO) | Saxon, Xalan, libxslt | Java (Spring), .NET, Node.js |
| 2 | ISO 20022 payment message → report | pacs.008, camt.053 XML | HTML + CSV + PDF | Saxon (Java), Saxon-JS (Node) | Java, .NET, Python |
| 3 | Large batch XML → styled reports | 10,000+ invoices XML | HTML/PDF per document | Saxon-EE (streaming) | Java Batch, Spring Batch |
| 4 | DocBook/DITA → multi-channel publishing | Technical manual XML | HTML5, PDF, EPUB | Saxon, Antenna House, XEP | Java, Node.js |
| 5 | SOAP response → preview / logging | SOAP envelope XML | HTML preview | Saxon, Xalan | Java EE, Spring WS |
3. Most popular XSLT processors on the server (2025–2026)
| Processor | Language / Platform | XSLT version | Streaming / High volume | Saxon-JS (browser) compatible | Still actively maintained? | Typical users |
|---|---|---|---|---|---|---|
| Saxon | Java, .NET, Node.js | 1.0, 2.0, 3.1 | Yes (Saxon-EE) | Yes (Saxon-JS) | Very active | Enterprise, banks, governments |
| libxslt | C, Python, PHP, Ruby | 1.0 only | Yes | No | Active | PHP, Python, legacy systems |
| Xalan | Java | 1.0 | Limited | No | Low activity | Old Java systems |
| Apache FOP | Java | XSL-FO 1.1 | Yes | No | Active | PDF generation from XSL-FO |
| Saxon-JS | Node.js / Browser | 3.0 | Limited | Yes | Active | Modern Node.js + browser |
Most common choice in 2025–2026
- Java world → Saxon-HE / Saxon-EE
- Node.js world → Saxon-JS or xslt3 (libxslt)
- PHP → libxslt / XSLTProcessor
- .NET → System.Xml.Xsl or Saxon.NET
4. Realistic server-side example 1 – Java + Spring Boot + Saxon
Goal: Receive GST e-invoice XML → transform to nice HTML + PDF
pom.xml (dependencies)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<dependency> <groupId>net.sf.saxon</groupId> <artifactId>Saxon-HE</artifactId> <version>12.5</version> </dependency> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>fop</artifactId> <version>2.9</version> </dependency> |
Controller
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@RestController @RequestMapping("/invoice") public class InvoiceController { private final TransformerFactory factory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl", null); @PostMapping(value = "/preview", consumes = MediaType.APPLICATION_XML_VALUE) public ResponseEntity<String> previewInvoice(@RequestBody String xmlContent) throws Exception { // 1. Parse input XML Source source = new StreamSource(new StringReader(xmlContent)); // 2. Load XSLT stylesheet Templates templates = factory.newTemplates(new StreamSource(new File("invoice-to-html.xsl"))); Transformer transformer = templates.newTransformer(); // 3. Transform → HTML string StringWriter htmlWriter = new StringWriter(); transformer.transform(source, new StreamResult(htmlWriter)); String html = htmlWriter.toString(); return ResponseEntity.ok() .contentType(MediaType.TEXT_HTML) .body(html); } } |
invoice-to-html.xsl (simplified real-world fragment)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" exclude-result-prefixes="ubl"> <xsl:output method="html" indent="yes" encoding="UTF-8"/> <xsl:template match="/ubl:Invoice"> <html> <head> <title>Tax Invoice Preview</title> <style> body { font-family: Arial; margin: 40px; } .invoice-header { text-align: center; margin-bottom: 30px; } table { width: 100%; border-collapse: collapse; margin: 20px 0; } th, td { border: 1px solid #ddd; padding: 10px; } th { background: #f0f0f0; } .total { font-weight: bold; font-size: 1.2em; } </style> </head> <body> <div class="invoice-header"> <h1>Tax Invoice</h1> <p>Invoice No: <xsl:value-of select="ubl:cbc:ID"/></p> <p>Date: <xsl:value-of select="ubl:cbc:IssueDate"/></p> </div> <table> <tr> <th>Item</th> <th>Quantity</th> <th>Rate</th> <th>Amount</th> </tr> <xsl:for-each select="ubl:cac:InvoiceLine"> <tr> <td><xsl:value-of select="ubl:cac:Item/ubl:cbc:Name"/></td> <td><xsl:value-of select="ubl:cbc:InvoicedQuantity"/></td> <td><xsl:value-of select="ubl:cac:Price/ubl:cbc:PriceAmount"/></td> <td><xsl:value-of select="ubl:cbc:LineExtensionAmount"/></td> </tr> </xsl:for-each> </table> <div class="total"> Total: <xsl:value-of select="ubl:cac:LegalMonetaryTotal/ubl:cbc:PayableAmount"/> <xsl:value-of select="ubl:cac:LegalMonetaryTotal/ubl:cbc:PayableAmount/@currencyID"/> </div> </body> </html> </xsl:template> </xsl:stylesheet> |
Summary – XSLT on the server in 2025–2026 – Quick Reference
| Scenario | Most common processor | Language / Framework | Output formats | Typical volume |
|---|---|---|---|---|
| GST e-Invoice → HTML + PDF | Saxon-EE | Java (Spring Boot) | HTML, PDF | Millions/month |
| ISO 20022 → report / PDF | Saxon | Java, .NET | HTML, PDF, CSV | High |
| DocBook/DITA → multi-channel | Saxon, Antenna House | Java | HTML5, PDF, EPUB | Medium–High |
| Batch XML → styled reports | Saxon-EE (streaming) | Java Batch, Spring Batch | PDF, HTML | Large files |
| Legacy SOAP → preview | Saxon, libxslt | Java EE, Node.js, PHP | HTML | Medium |
Would you like to continue with one of these next?
- Identity transform + small modifications (very common pattern)
- Passing parameters from Java/Node/PHP to XSLT
- Error handling & validation during transformation
- Streaming transformation (Saxon-EE) for very large files
- XSLT 3.0 features on server (group-by, JSON output, higher-order functions)
- Real-world example — GST e-invoice → beautiful HTML + PDF
Just tell me which direction feels most useful or interesting for you right now! 😊
