Rubrol vs Mustangproject: The Architectural Case for Retiring the 300MB Java JVM in Modern Invoicing
Mustangproject has long been the reference open-source implementation for ZUGFeRD in Germany. But deploying a heavy Java Virtual Machine (JVM) inside modern microservices creates painful memory spikes, slow cold starts, and architectural fragility. Here is the engineering breakdown of how Rubrol replaces the JVM with an atomic 14ms Typst pipeline.
The Legacy Giant: Mustangproject in Production
If you have worked with German e-invoicing anytime in the last 8 years, you have seen Mustangproject. Built by Jochen Stärk and the ZUGFeRD community, it is an impressive open-source Java library wrapping Apache PDFBox, Saxon-HE, and veraPDF to inject and validate CII XML within PDF/A-3 files.
For traditional enterprise Java applications running on persistent Tomcat or WildFly servers, Mustangproject did its job. But in 2026, document generation lives in ephemeral cloud containers, Kubernetes pods, and serverless runtimes.
And that is where the JVM architecture hits a brick wall.
Flaw 1: Mustangproject Cannot Actually Render Invoices
The biggest misconception among engineering teams evaluating Mustangproject is assuming it generates PDF invoices.
It does not. Mustangproject is strictly an XML generator, metadata injector, and validator. It requires an already-existing PDF input:
// Mustangproject CLI requires a pre-rendered PDF:
java -jar Mustang-CLI.jar --action combine \
--source-pdf existing_invoice.pdf \
--out compliant_zugferd.pdf \
--xml factur-x.xml
This forces you to maintain two completely separate engines:
- A visual rendering engine (usually Headless Chrome, wkhtmltopdf, or Weasyprint) to generate
existing_invoice.pdf. - A Java runtime (Mustangproject) to parse the PDF, unpack the DOM with Apache PDFBox, inject the XML into the Catalog dictionary, and rewrite the PDF binary.
With Rubrol, there is no Frankenstein pipeline. Rubrol compiles your visual invoice template (in mathematical Typst markup) and writes the certified PDF/A-3b container with embedded Factur-X / ZUGFeRD XML in a single atomic in-memory compilation pass.
Flaw 2: The 300MB Memory Tax & Docker OOM Kills
Running Java in lightweight container environments has always been fraught with memory issues. When Mustangproject boots inside a container, the JVM allocates its runtime heap, class metadata, and garbage collection structures.
Under concurrent request spikes, Apache PDFBox buffers entire document object trees into memory. If your Docker pod is capped at 512MB RAM, the JVM often fails to trigger GC in time, causing the Linux kernel cgroup OOM-killer to immediately terminate the container with exit code 137.
Engineering teams typically respond by over-provisioning container memory to 1GB or 2GB per replica, driving cloud infrastructure bills up for a task as simple as writing an invoice.
| Architectural Metric | Mustangproject (Java / PDFBox) | Rubrol (Rust / Typst Native) |
|---|---|---|
| Core Runtime | Java 17/21 JRE (~350MB base) | Zero runtime / Native static binary |
| Container Memory (Idle) | 140MB - 220MB | < 14MB |
| Container Memory (Under Load) | 350MB - 650MB (Heap + PDFBox DOM) | < 28MB |
| Cold Boot Time | 1,800ms - 3,200ms (JVM warm-up) | 8ms (Instant serverless wake) |
| Render + Embed Latency | 450ms - 1,100ms (Injection only) | 14ms (Full document render + embed) |
| Visual Document Layout | ❌ None (Requires external engine) | ✅ Built-in Typst typesetting engine |
| Integration Interface | Java classpath or CLI subprocess | REST API, Python SDK, CLI, Webhook |
Flaw 3: Subprocessing Hell vs Native REST API
If your microservice backend is written in Node.js, Python, Go, or Ruby, integrating Mustangproject requires spawning a subprocess:
// Node.js example spawning Java subprocess
const { execFile } = require('child_process');
execFile('java', ['-jar', 'Mustang-CLI.jar', '--action', 'combine', ...], (error, stdout) => {
// Heavy fork/exec overhead: 1.5 seconds per invoice
});
Every single invoice invocation pays the operating system cost of fork(), launching a fresh JVM, loading hundreds of class definitions, reading from disk, and exiting.
Rubrol runs as an in-memory HTTP sidecar or direct CLI that accepts JSON payloads and streams back binary PDF/A-3b documents over keep-alive HTTP/1.1 or HTTP/2 sockets in 14 milliseconds.
The Verdict
Mustangproject is a pioneer of the German ZUGFeRD specification, and for legacy Java enterprise applications, it remains a capable validator.
However, for modern engineering teams building automated billing, SaaS platforms, or high-throughput invoicing pipelines, Rubrol replaces the dual-toolchain headache with a single, sub-15ms, memory-safe engine that drops your container footprint by 95%.