Article

Validating ZUGFeRD with Mustang — what passes, what fails, and why

Mustang is the de-facto Factur-X / ZUGFeRD reference checker. A walkthrough of the common failure modes when embedding CII XML in a PDF/A-3 — and how to verify before you ship.

If you’re sending e-invoices to a German B2B customer in 2026, the file is either ZUGFeRD-compliant or it’s bounced at receipt. Same in France with Factur-X. The format is a PDF/A-3 wrapper with an EN 16931 CII XML attached — generating it from scratch is non-trivial, and validating it requires a reference engine.

That reference engine, in practice, is Mustang (mustangproject.org) — an open-source Java project that extracts the embedded XML from a PDF/A-3 and validates it against EN 16931 Schematron. Mustang has the deepest support for ZUGFeRD and Factur-X of any open-source tool, and it’s what most independent verifiers run.

This post walks through the failure modes Mustang flags — and a faster way to run it.

What Mustang actually checks

When you feed a Factur-X or ZUGFeRD PDF into Mustang, it does roughly:

  1. Extract the embedded file. PDF/A-3 stores attachments in the /EmbeddedFiles name tree. Mustang looks for the canonical filename (factur-x.xml for Factur-X, zugferd-invoice.xml for ZUGFeRD 2.x) and lifts the bytes.
  2. Check the AFRelationship. The attached file must be declared with AFRelationship="Alternative" per the Factur-X / ZUGFeRD baseline. Anything else (Source, Data, Supplement) fails.
  3. Check the XMP namespace and version. Factur-X 1.0 uses urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#. ZUGFeRD 2.x uses urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#. A wrong namespace or version string fails.
  4. Parse the XML as Cross-Industry Invoice (CII). It must be well-formed XML and start with the correct CII root element (rsm:CrossIndustryInvoice).
  5. Run EN 16931 Schematron. This is the bulk of the validation: ~200 business rules covering field semantics, mandatory codes, totals math, VAT logic, party identifiers, etc.

Pass = invoice is acceptable to any EN 16931-conformant AP system across the EU. Fail = your customer’s AP automation will reject the invoice on receipt and the AR team gets a manual exception.

The five failure modes we see most

These come up repeatedly on the validator Mustang side when teams test their first e-invoices.

1. Wrong AFRelationship

ERROR: Embedded file factur-x.xml uses AFRelationship="Source",
expected "Alternative".

The PDF spec allows several relationship types for attached files. Factur-X / ZUGFeRD specifically require Alternative — meaning the attached XML is an alternative representation of the visible PDF content. If your PDF generator is set to use Data (the default in many libraries), Mustang fails immediately. The visible PDF still renders correctly; the structured payload is invisible to the AP system.

2. Wrong / missing XMP namespace

ERROR: XMP metadata missing fx:DocumentType or fx:DocumentFileName under
namespace urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#.

The PDF’s XMP packet must declare which Factur-X profile this is (e.g. MINIMUM, BASIC, EN 16931, EXTENDED) and what filename to look for. Easy to miss when you write the PDF/A-3 wrapper by hand; gPdf’s /api/v1/e-invoice/render endpoint auto-emits these.

3. CII XML well-formed but EN 16931 Schematron fails

ERROR: BR-CO-25 — In an invoice (BR-01) the
  ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime is required when
  ram:DocumentTypeCode is 380.

This is the bulk of real-world failures. Your XML is valid syntactically; the business rules fail. EN 16931 Schematron rules have stable IDs (BR-01, BR-CO-25, etc.) that you can look up in the EN 16931 specification. The common ones:

  • BR-01: invoice must have a unique invoice number.
  • BR-04: invoice must have an issue date.
  • BR-05: invoice must have an invoice type code.
  • BR-CO-25: payment terms required when document type is “Commercial invoice”.
  • BR-Z-01: VAT category codes must be one of S, Z, E, AE, K, G, O, L, M.

Fix the source data; rebuild; re-validate.

4. PDF/A wrapper doesn’t actually validate

INFO: CII XML extracted and validates against EN 16931.
ERROR: PDF/A-3b conformance check failed: missing Output Intent.

This is the case where Mustang’s XML check passes but the underlying PDF/A-3 wrapper fails. Common cause: someone wrote the XML correctly but emitted a regular PDF instead of PDF/A-3 — the embedded file is present, but the archival wrapper rules aren’t met. The validator at gpdf.com/validator/ catches this by running veraPDF in parallel: PDF/A-3 fail surfaces in the veraPDF column while Mustang reports XML pass.

5. Encoding / declaration mismatch

ERROR: XML declares <?xml version="1.0" encoding="UTF-8"?> but the
embedded byte stream is UTF-8 with BOM. Mustang strict mode rejects BOM.

A surprisingly common issue when XML is generated by a tool that emits a UTF-8 BOM and then embedded raw. The fix: strip the BOM before embedding (gPdf’s e-invoice endpoint normalises this).

How to run Mustang without installing Java

Installing Java + the Mustang CLI is fine for a one-off check. For ongoing verification — every time you generate an invoice, every CI run that asserts e-invoice compliance — it’s friction you don’t need.

gpdf.com/validator/ runs Mustang in the browser:

  1. Drag your Factur-X / ZUGFeRD PDF onto the upload zone.
  2. The validator extracts the embedded XML and runs Mustang’s Schematron engine on it (compiled to JavaScript / WebAssembly, runs in the Cloudflare Worker).
  3. Mustang’s report comes back side-by-side with veraPDF’s PDF/A-3 report (since both layers need to pass).
  4. Download the JSON report for your QA evidence.

No login. No quota. The same Mustang you’d install via Maven, just served as a free public service.

TL;DR

Mustang flags 5 common failure modes; most of them collapse to “the file was generated by a tool that doesn’t emit a fully-conformant Factur-X / ZUGFeRD PDF/A-3”. The gPdf E-invoice API emits one in a single call. The validator verifies the result in two engines (Mustang + veraPDF) in parallel.

Most of the bugs Mustang catches are issues with the wrapper or the AFRelationship — not the XML semantics. Generating the file correctly is most of the battle; the validator is the receipt that proves you did.