Why XRechnung matters for Java teams
XRechnung is Germany's national CIUS (Core Invoice Usage Specification) of EN 16931, maintained by KoSIT (Koordinierungsstelle für IT-Standards). It takes the European core invoice standard and adds roughly two dozen German business rules (the BR-DE codes), makes several optional EN 16931 fields mandatory, and defines the submission conventions for German government portals such as ZRE and OZG-RE. XRechnung 3.0 has been the version in force since February 2024.
Three properties of the format shape every Java integration:
It is pure XML. Unlike ZUGFeRD or Factur-X, there is no PDF wrapper. The deliverable is an .xml file, which means your users cannot visually check an invoice before submission without a dedicated rendering step.
The Leitweg-ID is mandatory. XRechnung requires a routing identifier in the buyer reference field (BT-10), enforced by rule BR-DE-15. The Leitweg-ID is assigned by the public sector buyer and communicated during onboarding; it is routing infrastructure, not invoice data, so it can never be extracted from a source document. Your system has to store it per buyer and inject it into every request.
Two syntaxes are legal. EN 16931 defines both a UBL and a CII binding, and XRechnung permits either. Portals accept both. The practical choice usually follows the rest of your stack: CII if you also produce ZUGFeRD or Factur-X (they embed CII), UBL if you also serve the Peppol network, where BIS Billing 3.0 is UBL-based. The API lets you pick per request, so the choice is not a lock-in.
The Java options: KoSIT validator and Mustangproject
Before reaching for an API, it is worth an honest look at what the Java ecosystem offers locally.
The KoSIT validator. KoSIT publishes the official validation tool as an open-source Java application, together with a separately versioned XRechnung scenario configuration. You run it as a standalone Jar against an XML file, embed it as a library, or start it in daemon mode as a local HTTP service. Because it executes the exact rule artefacts the government portals use, a pass here is as authoritative as local validation gets. The costs: it is validation only, its output is an XML report you parse yourself, and the validator and its scenario configuration are versioned independently, so every KoSIT release (a new XRechnung version each year, Schematron patch releases in between) is a maintenance event you own. Run it before the rules change and compliant invoices start bouncing.
Mustangproject. The most established open-source Java library for German e-invoicing. Its core strength is ZUGFeRD and Factur-X hybrid PDFs, and it can also write XRechnung CII XML from its invoice model. It is actively maintained and free, but XRechnung is not its centre of gravity: there is no PDF-to-XRechnung conversion, no rendering of XRechnung XML into a review PDF, and validation depth depends on the rule artefacts bundled with the release you ship.
phive. Philip Helger's validation engine executes XSD plus Schematron rule sets in-process on the JVM, with published rule sets for XRechnung among many others. A good fit if you want library-style validation inside your own service, with the same caveat: you track rule-set releases yourself.
The honest summary: if all you need is validation and you are comfortable owning the KoSIT release cycle, Java can do that locally better than any other ecosystem. What no local option covers is the rest of the lifecycle: creating validated XRechnung from structured data in either syntax, converting supplier PDFs, rendering XML for human review, and extracting structured data from incoming files. That is the gap the REST API closes, and the comparison is picked up again below.
Setting up the Java HTTP client
The examples use java.net.http.HttpClient, in the JDK since Java 11, and text blocks, so Java 17 or later is assumed. No Maven dependency is needed for the HTTP calls; the validation section adds Jackson for JSON parsing.
Sign up for a free InvoiceXML account and you will receive 100 free credits on signup, no credit card required. Keep the API key out of source: an environment variable, a properties file outside version control, or your secrets manager.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
String apiKey = System.getenv("INVOICEXML_API_KEY");
String baseUrl = "https://api.invoicexml.com";
The client is immutable and thread-safe; create it once and share it across your application.
One JDK quirk to know up front: HttpClient has no built-in multipart/form-data support. The upload endpoints (validate, transform, render) need a small multipart body builder, shown once in the validation section and reused afterwards. If you are on Spring, RestClient handles multipart for you; see the Spring Boot section.
Create XRechnung invoices in Java
Send your invoice as JSON to POST /v1/create/xrechnung. Totals and the VAT breakdown are computed from the line items, and the response is an XRechnung 3.0 CII document that has already passed the EN 16931 Schematron and the KoSIT rules before delivery.
String payload = """
{
"invoice": {
"invoiceNumber": "LR-2026-04712",
"issueDate": "2026-07-01",
"dueDate": "2026-07-31",
"currency": "EUR",
"buyerReference": "04011000-1234567890-06",
"purchaseOrderReference": "BM-2026-PO-00142",
"seller": {
"name": "Weber Consulting GmbH",
"vatIdentifier": "DE334512678",
"postalAddress": { "line1": "Kaiserstraße 5", "postCode": "60311", "city": "Frankfurt am Main", "country": "DE" },
"contact": { "name": "Max Weber", "phone": "+49 69 12345678", "email": "[email protected]" },
"electronicAddress": { "identifier": "DE334512678", "schemeId": "0204" }
},
"buyer": {
"name": "Bundesministerium für Digitales",
"postalAddress": { "line1": "Wegbereiterstraße 8", "postCode": "53113", "city": "Bonn", "country": "DE" },
"electronicAddress": { "identifier": "991-00001-08", "schemeId": "0204" }
},
"paymentDetails": {
"paymentMeansCode": 58,
"paymentAccountIdentifier": "DE12500105170648489890"
},
"lines": [
{
"quantity": 85,
"unitCode": "HUR",
"item": { "name": "IT-Beratung Digitalisierungsstrategie" },
"priceDetails": { "netPrice": 100.00 },
"vatInformation": { "rate": 19, "categoryCode": "S" }
}
]
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/v1/create/xrechnung"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<byte[]> response =
http.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() == 200) {
Files.write(Path.of("invoice-xrechnung.xml"), response.body());
} else {
System.err.println(new String(response.body()));
}
Full create example on GitHub →
Two things in this payload are XRechnung-specific and worth calling out:
buyerReference carries the Leitweg-ID. This maps to BT-10 and is what BR-DE-15 checks. Store it per public sector buyer in your system; a missing or empty value fails generation with a structured error before any XML is produced.
Seller contact and electronic addresses are mandatory. XRechnung requires the full seller.contact group (name, phone, email) and electronic addresses for both parties, fields that are optional under base EN 16931. The create endpoint reference marks every required field.
Choosing UBL or CII syntax
POST /v1/create/xrechnung always emits the CII syntax. If your stack is UBL-based, for example because you also deliver over Peppol, create XRechnung in UBL syntax instead via POST /v1/create/ubl with the profile option:
{
"invoice": { "..." : "same invoice model as above" },
"options": { "profile": "xrechnung" }
}
Both outputs are legally equivalent XRechnung and both pass the same KoSIT rules; pick the syntax your recipient or portal integration expects.
Validate XRechnung invoices in Java
Before an invoice goes to ZRE, OZG-RE, or a state portal, validate it. POST /v1/validate/xrechnung auto-detects CII or UBL syntax and runs three layers: the XSD for the detected syntax, the EN 16931 core Schematron, and the German BR-DE rules from the KoSIT Schematron in force. It accepts XML generated by any tool, so it also works as an independent check on Mustangproject or hand-built output, for example in a JUnit suite on every build.
First, the multipart body builder that java.net.http is missing. It is around 20 lines and is reused by every upload endpoint in this guide:
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
static HttpRequest.BodyPublisher multipartBody(
Path file, String boundary, Map<String, String> fields) throws IOException {
List<byte[]> parts = new ArrayList<>();
for (var field : fields.entrySet()) {
parts.add(("--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"" + field.getKey() + "\"\r\n\r\n"
+ field.getValue() + "\r\n").getBytes(StandardCharsets.UTF_8));
}
parts.add(("--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\""
+ file.getFileName() + "\"\r\n"
+ "Content-Type: application/octet-stream\r\n\r\n")
.getBytes(StandardCharsets.UTF_8));
parts.add(Files.readAllBytes(file));
parts.add(("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));
return HttpRequest.BodyPublishers.ofByteArrays(parts);
}
The validation call, with Jackson (com.fasterxml.jackson.core:jackson-databind) deserializing straight into records:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
@JsonIgnoreProperties(ignoreUnknown = true)
public record ValidationResult(
boolean valid,
String detail,
ValidationData data,
List<ValidationFinding> errors,
List<ValidationFinding> warnings) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record ValidationData(
Boolean schemaValid,
Boolean schematronValid,
String profile,
String customizationId) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record ValidationFinding(
String rule,
Integer line,
String message,
String layer,
List<String> btCodes,
List<String> fields,
String raw) {}
static ValidationResult validateXrechnung(
HttpClient http, String baseUrl, String apiKey, Path xmlFile)
throws IOException, InterruptedException {
String boundary = "----" + UUID.randomUUID();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/v1/validate/xrechnung"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(multipartBody(xmlFile, boundary, Map.of()))
.build();
HttpResponse<String> response =
http.send(request, HttpResponse.BodyHandlers.ofString());
return new ObjectMapper()
.readValue(response.body(), ValidationResult.class);
}
Usage:
ValidationResult result =
validateXrechnung(http, baseUrl, apiKey, Path.of("invoice-xrechnung.xml"));
if (result.valid()) {
System.out.println("Valid XRechnung (" + result.data().customizationId() + ")");
} else {
System.out.println("Validation failed with " + result.errors().size() + " error(s):");
for (ValidationFinding err : result.errors()) {
System.out.println(" [" + err.rule() + "] (" + err.layer() + ") " + err.message());
}
}
Both valid and invalid invoices return HTTP 200; branch on valid in the response body, not on the status code. Each finding is tagged with the layer it came from: xsd, en16931, or cius for the German BR-DE rules. That distinction is worth surfacing to users, because a cius failure means the document is a fine EN 16931 invoice that specifically misses a German requirement. The classic example:
{
"valid": false,
"detail": "Validation failed with 1 error(s)",
"data": {
"schemaValid": true,
"schematronValid": false,
"profile": "xrechnung",
"customizationId": "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_3.0"
},
"errors": [
{
"rule": "BR-DE-15",
"line": null,
"layer": "cius",
"message": "The buyer reference (Leitweg-ID) is missing. XRechnung requires a buyer reference for invoices to German public sector buyers.",
"btCodes": ["BT-10"],
"fields": ["buyerReference"],
"raw": "[BR-DE-15] The element \"Buyer reference\" (BT-10) must be provided."
}
],
"warnings": []
}
Note this endpoint accepts XML only. To validate a ZUGFeRD hybrid PDF that declares the XRechnung reference profile, upload the PDF to /v1/validate/zugferd instead; the embedded XML is routed to the same KoSIT rules automatically.
Convert PDF invoices to XRechnung
When the source is a PDF, an image, or an office document rather than structured data, POST /v1/transform/to/xrechnung runs an AI extraction pipeline over the file, including scanned and photographed documents, and returns validated XRechnung XML.
One parameter is non-negotiable: BuyerReference. The Leitweg-ID is assigned by the buyer and is not on the source document, so the endpoint requires it as an explicit form field and rejects the request up front if it is missing.
static byte[] pdfToXrechnung(
HttpClient http, String baseUrl, String apiKey, Path pdfFile, String leitwegId)
throws IOException, InterruptedException {
String boundary = "----" + UUID.randomUUID();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/v1/transform/to/xrechnung"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(multipartBody(pdfFile, boundary,
Map.of("BuyerReference", leitwegId, "syntax", "cii")))
.build();
HttpResponse<byte[]> response =
http.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
throw new IllegalStateException(new String(response.body()));
}
return response.body();
}
byte[] xml = pdfToXrechnung(http, baseUrl, apiKey,
Path.of("supplier-invoice.pdf"), "04011000-1234567890-06");
Files.write(Path.of("supplier-xrechnung.xml"), xml);
The syntax field selects cii or ubl output. Accepted inputs are PDF (native and scanned), JPEG, PNG, TIFF, WEBP, HEIC, DOCX, and XLSX.
Render XRechnung as a PDF
XRechnung has no visual layer, so before an invoice is submitted (or when an incoming one needs human review) there is nothing to open except several hundred lines of XML. POST /v1/render/xrechnung/to/pdf maps every EN 16931 business term to a clean, readable PDF, with the Leitweg-ID labelled in the buyer reference section. Both syntaxes are detected automatically.
static byte[] renderXrechnungPdf(
HttpClient http, String baseUrl, String apiKey, Path xmlFile)
throws IOException, InterruptedException {
String boundary = "----" + UUID.randomUUID();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/v1/render/xrechnung/to/pdf"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(multipartBody(xmlFile, boundary, Map.of()))
.build();
HttpResponse<byte[]> response =
http.send(request, HttpResponse.BodyHandlers.ofByteArray());
return response.body();
}
byte[] pdf = renderXrechnungPdf(http, baseUrl, apiKey, Path.of("invoice-xrechnung.xml"));
Files.write(Path.of("invoice-preview.pdf"), pdf);
The rendered PDF is for visual review only and has no legal standing. The XRechnung XML remains the authoritative document; never submit the preview to a portal.
For the reverse direction, importing incoming XRechnung files into your ERP, POST /v1/extract/json returns the invoice as normalised JSON regardless of whether the input was CII or UBL syntax. See the extraction reference.
Spring Boot integration
In a Spring Boot 3.2+ service, RestClient replaces the manual multipart plumbing entirely. Register a client with the base URL and key, then wrap the operations in a service bean:
// application.yml
// invoicexml:
// base-url: https://api.invoicexml.com
// api-key: ${INVOICEXML_API_KEY}
@Configuration
public class InvoiceXmlConfig {
@Bean
RestClient invoiceXmlClient(
@Value("${invoicexml.base-url}") String baseUrl,
@Value("${invoicexml.api-key}") String apiKey) {
return RestClient.builder()
.baseUrl(baseUrl)
.defaultHeader("Authorization", "Bearer " + apiKey)
.build();
}
}
@Service
public class XrechnungService {
private final RestClient client;
public XrechnungService(RestClient invoiceXmlClient) {
this.client = invoiceXmlClient;
}
public byte[] create(Object invoice) {
return client.post()
.uri("/v1/create/xrechnung")
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("invoice", invoice))
.retrieve()
.body(byte[].class);
}
public ValidationResult validate(byte[] xrechnungXml) {
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("file", new ByteArrayResource(xrechnungXml) {
@Override
public String getFilename() { return "invoice.xml"; }
});
return client.post()
.uri("/v1/validate/xrechnung")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(form)
.retrieve()
.body(ValidationResult.class);
}
}
A typical pipeline calls create, then validate as a guard, and archives the XML only when valid is true. Since generated documents are validated server-side before delivery, the second call is a belt-and-braces check that mainly earns its keep on data assembled from multiple upstream systems.
KoSIT validator vs REST API: when to use which
The build-versus-buy question is fairer in Java than anywhere else, so here is the honest split:
The KoSIT validator earns its place when validation is your only requirement, you want the official artefacts running on your own infrastructure, and you have the discipline to track KoSIT releases and update the scenario configuration on time. As a Java tool it slots into a Java stack with no impedance mismatch.
The API becomes compelling when you need more than a pass/fail on existing XML:
The full lifecycle. Creation from structured data in either syntax, PDF-to-XRechnung conversion with AI extraction, rendering for human review, and structured extraction from incoming files. None of the local options covers these.
Structured, typed results. Validation findings arrive as JSON with rule id, layer, plain-language message, business term codes, and field paths, ready to surface in a UI, instead of an XML report you post-process.
Zero rule maintenance. New XRechnung versions and KoSIT Schematron releases are deployed before their effective dates with no change on your side. With local tooling, that calendar is yours to own, and missing a release means compliant-looking invoices get rejected at the portal.
More than one format. The same integration covers ZUGFeRD, Factur-X, Peppol UBL, and CII. If your customers span B2G and B2B, one HTTP client handles all of it; the companion guide to ZUGFeRD in Java covers the hybrid PDF side with the same multipart helper.
Stateless processing. Documents are processed in memory and purged on response delivery. Nothing is written to disk, nothing is logged, and no invoice data is used for model training.
The two also combine well: plenty of teams generate with the API and keep the KoSIT validator in CI as an independent second opinion, or generate locally with Mustangproject and use /v1/validate/xrechnung as the pre-submission gate.
Complete endpoint reference for Java
| Operation | Endpoint | Input | Output |
| Create XRechnung (CII) | POST /v1/create/xrechnung | JSON | XRechnung 3.0 XML |
| Create XRechnung (UBL) | POST /v1/create/ubl + options.profile: xrechnung | JSON | XRechnung UBL XML |
| PDF to XRechnung (AI) | POST /v1/transform/to/xrechnung | PDF, image, DOCX, XLSX | XRechnung XML |
| Validate XRechnung | POST /v1/validate/xrechnung | XRechnung XML (CII or UBL) | Validation JSON |
| Render as PDF | POST /v1/render/xrechnung/to/pdf | XRechnung XML | PDF binary |
| Extract as JSON | POST /v1/extract/json | XRechnung XML | Structured JSON |
| XRechnung to plain UBL | POST /v1/convert/xrechnung/to/ubl | XRechnung XML | EN 16931 UBL XML |
| XRechnung to plain CII | POST /v1/convert/xrechnung/to/cii | XRechnung XML | EN 16931 CII XML |
Full OpenAPI 3.1 schema: api.invoicexml.com/v1/openapi | Interactive API explorer: api.invoicexml.com/v1/scalar
Get started
Every operation in this guide is a single HTTP call with the JDK's own client: no SDK, no Saxon, no scenario configuration to babysit. Clone the examples repository, set INVOICEXML_API_KEY, and you have XRechnung output in minutes:
InvoiceXML/facturx-api-examples/java →
Create a free InvoiceXML account → get 100 credits for free, no credit card required.
Related resources:
InvoiceXML is a REST API for European e-invoice compliance covering XRechnung, ZUGFeRD, Factur-X, Peppol UBL, and CII. Stateless processing, GDPR compliant by architecture, and callable from any Java application: Spring Boot, Jakarta EE, Quarkus, or a plain JVM service.