en16931-basics
September 7, 2026 · View on GitHub
If this project saved you some time or made your day a little easier, a star would mean a lot — it helps others find it too.
The shared building blocks of the EN 16931 tooling: the facts about the standard that en16931-cii2ubl, en16931-purifier and en16931-ubl2cii all need, held in exactly one place.
This is a Java 17+ library. Its only dependencies are ph-commons and ph-xml — deliberately
neither ph-cii nor ph-ubl. That is what keeps this artefact model agnostic and cheap to depend on.
This library is licensed under the Apache License Version 2.0.
What is in here
Everything in this library changes when the standard changes, and stays put when our code changes:
| Class | Contents |
|---|---|
EEN16931Edition | The 2017 and the 2026 edition, their BT-24 specification identifiers and the BT-24 based detection |
CEN16931Syntax | The XML namespace URIs, the customary prefixes and the document element names of UBL and CII |
EEN16931SyntaxKind | UBL Invoice, UBL Credit Note, CII — the syntax kind, independent of the syntax version |
EEN16931DocumentType | Syntax kind × syntax version, as data only |
SpecificationIdentifierReader | Reads BT-24 from a file via SAX or from an existing DOM tree |
EN16931CodeLists | The UNTDID code list subsets that EN 16931 uses, and the mappings between the UBL and the CII code list of the same business term |
EEN16931…Code | One enum per code list of the EN 16931 code list workbook — see The code lists |
ConversionHelper | ifNotNull and ifNotEmpty |
What is not in here: conversion or purification logic, anything typed to a JAXB model, and CLI code.
Maven usage
Replace x.y.z with the effective version you want to use:
<dependency>
<groupId>com.helger</groupId>
<artifactId>en16931-basics</artifactId>
<version>x.y.z</version>
</dependency>
The EN 16931 edition
Each edition prescribes exactly one CII release and one UBL version:
| Edition | BT-24 specification identifier | CII | UBL |
|---|---|---|---|
EEN16931Edition.EN2017 | urn:cen.eu:en16931:2017 | D16B | 2.1 |
EEN16931Edition.EN2026 | urn:cen.eu:en16931:2026 | D25A | 2.5 |
The edition cannot be told from the XML namespaces. CII D16B, D22B and D25A all use the identical namespace URIs, and so do all UBL 2.x versions. The XML Schema does not help either, because a CII D16B instance also validates against the CII D25A XSD. The only reliable discriminator is BT-24 (Specification identifier), which is mandatory in every EN 16931 document.
final EEN16931Edition eEdition = EEN16931Edition.detect (new File ("invoice.xml"));
detect accepts a File, a org.w3c.dom.Document or a document element, determines the syntax
kind from the document element and reads BT-24 from cbc:CustomizationID for UBL respectively from
rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID for CII. It
never unmarshals, because the correct JAXB model is exactly what is not known yet. It returns null
if BT-24 is absent or belongs to no known edition — a legacy ZUGFeRD 1.0 document carries
urn:ferd:CrossIndustryDocument:invoice:1p0:comfort, for example.
The reading itself lives in SpecificationIdentifierReader:
| Source | How it is read |
|---|---|
File, IReadableResource, InputSource | SAX, and the parser is stopped as soon as BT-24 is known |
org.w3c.dom.Node | a DOM peek, for callers that hold the document in memory anyway |
BT-24 sits close to the start of the document in both syntaxes, so the SAX variant never reads the bulk of the file. The flip side is that XML which is not well formed behind BT-24 stays unnoticed — determining the edition does not imply that the document is valid in any way.
BT-24 is matched by prefix, because customizations append their own identifier:
// both yield EN2017
EEN16931Edition.getFromSpecificationIdentifierOrNull ("urn:cen.eu:en16931:2017");
EEN16931Edition.getFromSpecificationIdentifierOrNull ("urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0");
The code lists
The source of truth for everything below is the workbook EN16931 code lists values v17b - used from 2026-05-15 of the
Registry of supporting artefacts to implement EN 16931.
Every sheet of that workbook is available as an enum, and EN16931CodeLists adds the classifying
helpers on top of them.
One enum per code list
| Business term | Code list | Enum |
|---|---|---|
| BT-3 | UNTDID 1001 Document type | EEN16931InvoiceTypeCode |
| BT-5, BT-6 | ISO 4217 Currency codes | EEN16931CurrencyCode |
| BT-8 | UNTDID 2005 (UBL) ↔ UNTDID 2475 (CII) | EEN16931DueDateTypeCode |
| BT-18-1, BT-128-1 | UNTDID 1153 Reference code qualifier | EEN16931ReferenceQualifierCode |
| BT-21 | UNTDID 4451 Text subject qualifier | EEN16931TextSubjectCode |
| BT-29-1, BT-30-1, BT-46-1, BT-47-1, BT-60-1, BT-61-1, BT-71-1, BT-157-1 | ISO/IEC 6523 Identifier scheme code | EEN16931ICDCode |
| BT-31, BT-48, BT-63 | VAT identifier scheme | EEN16931TaxSchemeCode |
| BT-32 | Tax registration scheme | EEN16931TaxRegistrationCode |
| BT-34-1, BT-49-1 | CEF EAS Electronic address scheme | EEN16931EASCode |
| BT-40, BT-55, BT-69, BT-80, BT-159 | ISO 3166-1 Country codes | EEN16931CountryCode |
| BT-81 | UNTDID 4461 Payment means | EEN16931PaymentMeansCode |
| BT-95, BT-102, BT-118, BT-151 | UNTDID 5305 Duty or tax or fee category | EEN16931TaxCategoryCode, EEN16931TaxCategorySchemeCode |
| BT-98, BT-140 | UNTDID 5189 Allowance codes | EEN16931AllowanceReasonCode |
| BT-105, BT-145 | UNTDID 7161 Charge codes | EEN16931ChargeReasonCode |
| BT-121 | CEF VATEX VAT exemption reason code | EEN16931VATEXCode |
| BT-125-1 | Mime codes | EEN16931MimeCode |
| BT-158-1 | UNTDID 7143 Item type identification code | EEN16931ItemTypeCode |
Every one of them carries getID() — the code as spelled in the workbook — plus everything else the
sheet has to say about the code, and offers a getFromIDOrNull(String) and a containsID(String)
backed by a map, so a lookup does not scan the list:
if (!EEN16931CurrencyCode.containsID (sCurrencyCode))
... // BT-5 is not a valid currency code
// "M" is "Tax for production, services and importation in Ceuta and Melilla" in UNTDID 5305,
// but "Liable for IPSI" in the EN 16931 semantic data model
EEN16931TaxCategoryCode.M.getSemanticModelName ();
Codes that are not valid Java identifiers get a leading underscore — EEN16931ICDCode._0002,
EEN16931InvoiceTypeCode._380 — everything else keeps the code as its name:
EEN16931CurrencyCode.EUR, EEN16931CountryCode.DE.
Three of the sheets pair a UBL code with a different CII code for the same business term, so
EEN16931DueDateTypeCode, EEN16931TaxSchemeCode and EEN16931TaxCategorySchemeCode carry both
and offer getUBLCode() / getCIICode() instead of a single getID().
The unit codes of BT-130 and BT-150 are not among them. UN/ECE Recommendation N°20 and N°21 together are 2162 codes, and an enum of that size does not compile: the static initializer exceeds the 65535 byte limit the JVM puts on a single method. Validate a unit code against ph-masterdata or the UN/ECE lists directly.
The classifying helpers
| Business term | Code list | API |
|---|---|---|
| BT-3 Invoice type code | UNTDID 1001 | INVOICE_TYPE_CODES, CREDIT_NOTE_TYPE_CODES, isInvoiceTypeCode, isCreditNoteTypeCode |
| BT-8 VAT point date code | UNTDID 2475 (CII) ↔ UNTDID 2005 (UBL) | mapDueDateTypeCodeCIIToUBL, mapDueDateTypeCodeUBLToCII |
| BT-81 Payment means type code | UNTDID 4461 | isPaymentMeansCodeCreditTransfer, isPaymentMeansCodePaymentCard, isPaymentMeansCodeDirectDebit |
| BT-17 / BT-18 / BG-24 document type code | UNTDID 1001 | isOriginatorDocumentReferenceTypeCode, isValidDocumentReferenceTypeCode, DOCUMENT_TYPE_CODE_SUPPORTING_DOCUMENT |
INVOICE_TYPE_CODES, CREDIT_NOTE_TYPE_CODES and the two BT-8 mappings are derived from the
enums, so the classification exists exactly once.
The implemented workbook version is available as CODE_LIST_VERSION and
CODE_LIST_EFFECTIVE_DATE, so a consumer can report what it is based on.
Notes that are easy to get wrong:
- The code lists are versioned by date and not by EN 16931 edition. The 2017 and the 2026 edition share them; there is no 2017 to 2026 delta in these lists.
- BT-3 is a subset of 62 codes, 49 of them an Invoice and 13 of them a Credit Note. No code
appears in both roles. The codes
471,472,473,500,501,502and503were added in v15 (used from 2025-05-15); an extract with 55 codes is a 2024 snapshot. - The EN 16931 validation artefacts accept
81on an Invoice, whereas every version of the code list has it as a Credit Note only. The code list wins here. - The two BT-8 mappings are an inverse pair derived from
EEN16931DueDateTypeCode, so the two directions cannot drift apart. - The workbook itself notes that the names of
VATEX-EU-DandVATEX-EU-Fdiffer from the VATEX code list proper. The correction needs a non backwards compatible change and is postponed to November 2026;EEN16931VATEXCodecarries the names of the workbook.
Building
Requires Java 17 or higher.
mvn clean install
News and noteworthy
v1.0.1 - 2026-09-07
- Added
EEN16931TaxSchemeCode.LOC, the BT-32 national tax registration pair UBLLOC/ CIIFC, somapTaxSchemeCodeUBLToCIIandmapTaxSchemeCodeCIIToUBLcover BT-32 as well as BT-31, BT-48 and BT-63 - Added
EN16931CodeLists.CREDITOR_REFERENCE_SCHEME_ID- the BT-90-1 scheme identifierSEPA, which is the only thing that tells BT-90 apart from the party identifiers BT-29, BT-46 and BT-60 in UBL - Added
EN16931CodeLists.NON_VAT_TAX_CODE_LIST_ID- the BT-177-1 / BT-193-1 list identifier5153 - Added
EN16931CodeLists.DOCUMENT_TYPE_CODE_LIST_ID- the BT-122-1-1 list identifier1001 - Added
EN16931CodeLists.MISSING_VALUE_PLACEHOLDER- the literalNonethat both syntax bindings prescribe where a syntax element is mandatory but its business term is absent, as in BT-13, BT-11-1 and the line references BT-132, BT-190, BT-192, BT-199 and BT-201
v1.0.0 - 2026-09-05
- Initial version
- Contains
EEN16931Editionwith the BT-24 based edition detection for UBL and CII - Contains
CEN16931Syntax,EEN16931SyntaxKindandEEN16931DocumentType - Contains
EN16931CodeListsbased on the code list valuesv17b(used from 2026-05-15) - Contains
SpecificationIdentifierReader, which reads BT-24 via SAX and stops the parser as soon as the value is known, instead of building a DOM tree of the whole document - Contains one enum per code list of the EN 16931 code list workbook
v17b, fromEEN16931AllowanceReasonCodetoEEN16931VATEXCode.EN16931CodeListsderives its BT-3 sets and its BT-8 mappings from them. The unit codes of BT-130 and BT-150 are the one exception — 2162 codes do not fit into a Java enum. - Contains
EEN16931DateFormatCodewith the UNTDID 2379 date format qualifiers the CII syntax binding uses, and the parsing and writing that goes with them EN16931CodeListsmaps the BT-31/BT-48/BT-63 tax scheme identifier between UBL and CII (mapTaxSchemeCodeCIIToUBLandmapTaxSchemeCodeUBLToCII), derived fromEEN16931TaxSchemeCodeEEN16931Editiontells the syntax version it prescribes —getDocumentType,getUBLSyntaxVersionandgetCIISyntaxVersion— andEEN16931DocumentTypenames itsgetEditionin returnEEN16931SyntaxKind.getFromNodeOrNulldetermines the syntax kind straight from a DOM document or document element
My personal Coding Styleguide | It is appreciated if you star the GitHub project if you like it.