Tutorial: ZUGFeRD & XRechnung mit Java

Wie man mit Java Spring Boot und Mustangproject in wenigen Minuten ein Tool zur Generierung von ZUGFeRD-PDFs und XRechnungen baut.

Veröffentlicht:
Kategorie:
Beitragsbild InvoiceForge: Tutorial für ZUGFeRD-PDFs und XRechnungen
Invoice Forge: Tutorial für ZUGFeRD-PDFs und XRechnungen

InvoiceForge: E-Rechnung mit Java

Elektronische Rechnungen gehören längst zum geschäftlichen Alltag. Seit die XRechnung für die öffentliche Verwaltung verpflichtend wurde und viele Unternehmen zusätzlich auf ZUGFeRD setzen, steigt der Anspruch an die Unternehmen solche elektronischen Rechnungen anzubieten.

Hier zeige ich dir am Beispiel unseres internen Projekts “InvoiceForge”, wie du in kurzer Zeit ein kleines Spring-Boot-basiertes Tool baust, das mithilfe von Mustangproject aus JSON- oder anderen strukturierten Eingaben vollautomatisch PDF/A-3-Rechnungen erzeugt, inklusive gültiger XRechnung- oder ZUGFeRD-XML.

 

Das Problem: kein guter XML-Export

Unternehmen erzeugen Rechnungen oft in ihrem ERP-System, in einer Billing-Software oder direkt in einem Microservice. Was aber häufig fehlt, ist ein standardkonformes Ausgabeformat.
XRechnung verlangt ein sehr präzises XML, während ZUGFeRD eine hybride Struktur nutzt: Ein PDF/A-3, in das ein XML-Dokument eingebettet ist.

Viele Systeme bieten diese Exportoptionen nicht direkt an – oder sie wären extrem aufwändig zu implementieren. Daher wollen wir dir zeigen, wie du dir deine eigenen XRechnungen und ZUGFeRD PDFs selber generieren kannst.

 

Das Setup in 4 Schritten

Damit der Einstieg leicht fällt, setze ich auf ein bewährtes, aber dennoch schlankes Setup. Ziel ist es, möglichst wenig Boilerplate zu benötigen und trotzdem ein produktionsreifes Tool zu erhalten.

1. Technologien

Für ein schlankes Setup nutzen wir Java SpringBoot mit Maven:

  • Java 17+
  • Spring Boot 3.x
  • Maven (funktioniert genauso mit Gradle)
  • Mustangproject ≥ 2.x (eine Java-Bibliothek für ZUGFeRD-/XRechnung-Generierung)
  • Optional:
    • Lombok
    • Spring Validation
    • Spring Web

2. Abhängigkeiten in der `pom.xml`

Der wichtigste Teil: Mustangproject.

XML
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/> </parent>
    <groupId>com.nterra</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>InvoiceForge</name>
    <description>Minimal Invoice Generator Service</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mustangproject</groupId>
            <artifactId>library</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3. Projektstruktur (minimal)

src/
 └─ main/
     ├─ java/com/nterra.invoiceforge/
     │   ├─ InvoiceController.java
     │   ├─ InvoiceService.java
     │   ├─ model/InvoiceRequest.java
     │   ├─ mapper/InvoiceMapper.java
     │   └─ InvoiceForgeApplication.java
     └─ resources/
         └─ application.properties

4. Starten

Shell
mvn spring-boot:run

Das war’s – damit läuft schon mal unser grundlegendes Projekt. Nun müssen wir nur noch den Service implementieren und diesen an den Controller anschließen.

Die Schritt-für-Schritt-Lösung

Im Kern macht unser Tool nur drei Dinge:

  1. Empfängt Daten über ein REST-Endpoint
  2. Mappt diese Daten auf eine Mustangproject-Transaktion
  3. Erzeugt ein PDF + XML und gibt es als Bytearray zurück

Ein POST-Request mit JSON und PDF geht rein → ein korrektes ZUGFeRD-/XRechnung-PDF kommt raus.

1. REST-Controller

Der Controller ist bewusst minimalistisch gehalten:

Java
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {

    private final InvoiceService invoiceService;

    public InvoiceController(InvoiceService invoiceService) {
        this.invoiceService = invoiceService;
    }

    @PostMapping(value = "/generate", consumes = 
MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<byte[]> generate(
            @RequestParam("pdf") MultipartFile pdfFile,
            @RequestParam("json") String invoiceJson) throws IOException {

        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new JavaTimeModule());

        InvoiceRequest request = 
                mapper.readValue(invoiceJson, InvoiceRequest.class);

        byte[] zipContent = invoiceService.generateInvoiceBundle(request, 
pdfFile);

        HttpHeaders headers = new HttpHeaders();
        // Setzt den MIME-Type auf ZIP
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        // Legt den Dateinamen für den Download fest
        headers.setContentDispositionFormData("attachment", 
"rechnungs_paket.zip");

        return new ResponseEntity<>(zipContent, headers, HttpStatus.OK);
    }
}

Unser InvoiceController benötigt für unser minimalistisches Beispiel nur ein Methode, die POST-Requests an /api/invoices/generate bearbeitet. Wir erwarten in diesem Request ein JSON mit mindestens-notwendigen Daten für die XRechnungsgenerierung sowie die Rechnungs-PDF damit unsere ZUGFeRD-PDF entstehen kann. Mittels eines simplen ObjectMappers lesen wir dann die Rechnungsdaten aus und mappen sie auf unser eigenes Java Request Model.

Java
@Data
public class InvoiceRequest {

    private String invoiceNumber;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDate issueDate;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDate deliveryDate;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDate dueDate;
    private String currency;

    // Seller 
    private String sellerName;
    private String sellerStreet;
    private String sellerPostalCode;
    private String sellerCity;
    private String sellerCountry;
    private String sellerVatId;

    // Buyer 
    private String buyerName;
    private String buyerStreet;
    private String buyerPostalCode;
    private String buyerCity;
    private String buyerCountry;
    private String buyerVatId;

    private List<InvoiceItem> items;

    private String netAmount;
    private String vatRate;
    private String vatAmount;
    private String grossAmount;

    @Data
    public static class InvoiceItem {
        private String name;
        private BigDecimal price;
        private BigDecimal quantity;
    }
}

Danach rufen wir mit diesem Request Objekt und der dazugehörigen PDF-Datei den Service auf.

2. Der Service: Mustangproject orchestrieren

Der Service ist konkret in drei Teile aufgeteilt:

  1. Die vom User bereitgestellte PDF laden
  2. Das mustangproject Invoice Objekt mittels des Mappers erzeugen
  3. XRechnungs-XML und ZUGFeRD PDF über den Exporter generieren lassen
Java
@Service
public class InvoiceService {

    public byte[] generateInvoiceBundle(InvoiceRequest request, MultipartFile 
pdfFile) throws IOException {

        // 1. PDF ladenbyte[] pdfInput = pdfFile.getInputStream().readAllBytes();

        // 2. Invoice Objekt erzeugen
        Invoice invoice = InvoiceMapper.mapToInvoice(request);

        // 3. Profil festlegen (WICHTIG für XRechnung!)
        Profile profile = Profiles.getByName("XRECHNUNG");

        byte[] finishedZip = new byte[0];

        try {
            // A. XML Generierung (XRechnung) mit ZUGFeRD2PullProvider
            ZUGFeRD2PullProvider xmlProvider = new ZUGFeRD2PullProvider();
            xmlProvider.setProfile(profile);
            xmlProvider.generateXML(invoice);

            byte[] finalXmlBytes = xmlProvider.getXML();

            // B. ZUGFeRD PDF Generierung
            OXExporterFromA1 exporter = new OXExporterFromA1()
                    .setProducer("InvoiceForge")
                    .setCreator("InvoiceForge")
                    .setZUGFeRDVersion(2)
                    .setProfile(profile)
                    .ignorePDFAErrors()
                    .load(pdfInput);

            exporter.setTransaction(invoice);

            ByteArrayOutputStream pdfBaos = new ByteArrayOutputStream();
            exporter.export(pdfBaos);
            byte[] finalPdfBytes = pdfBaos.toByteArray();

            // C. Beides in ein ZIP packen
            ByteArrayOutputStream zipBaos = new ByteArrayOutputStream();
            try (ZipOutputStream zos = new ZipOutputStream(zipBaos)) {

                // 1. PDF hinzufügen
                ZipEntry pdfEntry = new ZipEntry("invoice.pdf");
                zos.putNextEntry(pdfEntry);
                zos.write(finalPdfBytes);
                zos.closeEntry();

                // 2. XML hinzufügen
                ZipEntry xmlEntry = new ZipEntry("xrechnung.xml");
                zos.putNextEntry(xmlEntry);
                zos.write(finalXmlBytes);
                zos.closeEntry();
            }

            finishedZip = zipBaos.toByteArray();

        } catch (Exception e) {
            throw new RuntimeException("Fehler bei der ZIP-Erstellung für 
XRechnung", e);
        }

        return finishedZip;
    }
}

Mustangproject übernimmt hier den Großteil der Arbeit. Die größte Herausforderung liegt im Mapping von Request Objekt zu mustangproject Invoice Objekt.

3. Mapper – das Herzstück

Hier werden die Eingangsdaten in eine Mustangproject-Transaktion überführt.

Java
public class InvoiceMapper {

    public static Invoice mapToInvoice(InvoiceRequest req) {
        Invoice invoice = null;

        try {
            invoice = new Invoice()
                    .setNumber(req.getInvoiceNumber())
                    .setIssueDate(toDate(req.getIssueDate()))
                    .setDeliveryDate(toDate(req.getDeliveryDate()))
                    .setDueDate(toDate(req.getDueDate()))
                    .setSender(createSeller(req))
                    .setRecipient(createBuyer(req));


            // Artikel / Positionen hinzufügen
            Invoice finalInvoice = invoice;
            req.getItems().forEach(item -> {
                Item newItem = new Item();
                newItem.setProduct(new Product(item.getName(), "No 
description.", "", new BigDecimal(0)));
                newItem.setPrice(item.getPrice());
                newItem.setQuantity(item.getQuantity());
                finalInvoice.addItem(newItem);
            });

        } catch (Exception e) {
            e.printStackTrace();
        }

        return invoice;
    }

    private static Date toDate(LocalDate localDate) {
        return java.sql.Date.valueOf(localDate);
    }

    private static TradeParty createSeller(InvoiceRequest req) {
        return new TradeParty(
                req.getSellerName(),
                req.getSellerStreet(),
                req.getSellerPostalCode(),
                req.getSellerCity(),
                req.getSellerCountry()
        );
    }

    private static TradeParty createBuyer(InvoiceRequest req) {
        return new TradeParty(
                req.getBuyerName(),
                req.getBuyerStreet(),
                req.getBuyerPostalCode(),
                req.getBuyerCity(),
                req.getBuyerCountry()
        );
    }
}

In diesem kleinen Beispiel werden nur die nötigsten Werte und Sub-Objekte für das Invoice Objekt befüllt. Für reale Rechnungen stehen in der Regel mehr Daten zu Verfügung womit der Mapper dann erweitert werden kann. Die übergebenen Daten reichen aber bereits für die Generierung einer validen XRechungs-XML- und ZUGFeRD PDF.
Diese generierten Dateien werden dann gezippt vom Service an den Controller und von diesem an den User zurückgegeben.

Das war’s! Schon ist unsere API fertig.

Los geht’s: Beispiel-Request

Ein Beispiel-Request für die fertige API sähe wie folgt aus:

JSON

{
  "invoiceNumber": "INV-2025-001",
  "issueDate": "2025-01-20",

  "sellerName": "Mustermann GmbH",
  "sellerStreet": "Beispielweg 1",
  "sellerPostalCode": "12345",
  "sellerCity": "Berlin",
  "sellerCountry": "DE",

  "buyerName": "ACME AG",
  "buyerStreet": "Hauptstraße 5",
  "buyerPostalCode": "98765",
  "buyerCity": "Hamburg",
  "buyerCountry": "DE",

  "items": [
    {
      "name": "Beratung",
      "quantity": 8,
      "price": 120.00
    }
  ]
}

Wenn wir dieses JSON nun um eine PDF ergänzen, kann es losgehen!

Das Fazit zum XRechnung Tool

Mit Java, Spring Boot und Mustangproject lässt sich in kurzer Zeit ein leistungsfähiges eRechnungs-Tool bauen.

InvoiceForge zeigt, dass man mit überschaubarem Aufwand:

  • ZUGFeRD- & XRechnungen erzeugen kann
  • einen klaren, wartbaren Codebase erhält
  • einen Microservice für reale Produktionssysteme baut

Weiterführende Links:

Info: ZUGFeRD vs. XRechnung

Die Einführung der verpflichtenden E-Rechnung ist kein bloßer bürokratischer Akt, sondern zahlt auf das Herzstück des deutschen Wachstumschancengesetzes ein. Ziel des Gesetzgebers ist es, durch die Digitalisierung von Geschäftsprozessen die Effizienz zu steigern und gleichzeitig Umsatzsteuerbetrug durch automatisierte, transparente Meldewege effektiv zu bekämpfen. Für Unternehmen bietet die Umstellung – trotz des initialen Aufwands – enorme Vorteile: Automatisierte Verarbeitung, der Wegfall manueller Erfassungsfehler, schnellere Zahlungsziele und eine signifikante Kostenersparnis bei Papier, Druck und Porto. Also all das, was uns von nterra die Digitalisierung so lieben lässt. 😉

Dabei begegnen dir in der Praxis vor allem zwei Formate, die beide auf der europäischen Norm EN 16931 basieren:

XRechnung ist ein rein strukturiertes XML-Format. Es ist der Standard für die öffentliche Verwaltung (B2G). Da es keine visuelle Komponente hat, ist es für Menschen ohne speziellen Viewer nicht lesbar.

ZUGFeRD (Zentraler User Guide des Forums elektronische Rechnung Deutschland) ist ein hybrides Format. Es besteht aus einer visuell lesbaren PDF-Datei (PDF/A-3), in die eine maschinenlesbare XML-Datei eingebettet ist. Das Beste aus beiden Welten: Lesbar für den Menschen und prozessierbar für die Maschine.

Warum setzen wir das Mustangproject ein?
Die Bibliothek abstrahiert diese Komplexität und erlaubt es uns, mit denselben Datenstrukturen sowohl ZUGFeRD-Konformität als auch (technisch gesehen) die Anforderungen der XRechnung zu erfüllen.

Hinweis: Die dargestellten Beispiele dienen ausschließlich zu Demonstrationszwecken. Für den produktiven Einsatz von XRechnung oder ZUGFeRD sind die jeweils gültigen Normen, Profile und gesetzlichen Anforderungen vollständig umzusetzen und durch geeignete Validierungsverfahren sicherzustellen.

Ähnliche Blogartikel

  • Observability – ein Gamechanger

    Erfahren Sie, wie Sie mit Observability volle Sichtbarkeit in den Systembetrieb bringen.

  • Neue Partner an Bord: Seeburger & Lobster erweitern unser Portfolio

    Wir begrüßen mit Seeburger und Lobster zwei neue Technologiepartner, die unser Portfolio im Bereich Datenintegration und Prozessdigitalisierung ideal ergänzen.

  • OAuth 2.0 und OpenID Connect: Grundlagen, Flows und Best Practices für moderne Authentifizierung

    Ein praxisorientierter Überblick über OAuth 2.0 und OpenID Connect – von den Grundlagen bis zu sicheren Implementierungen.