Eine gültige E-Rechnung (XRechnung oder ZUGFeRD/Factur-X) erzeugst du aus Java am robustesten über eine E-Rechnung API statt durch eigenes XML-Bauen: Du sendest strukturierte Rechnungsdaten per java.net.http.HttpClient, der Dienst rechnet die Summen, vergibt die Nummer und rendert das EN-16931-konforme Dokument. Der Flow sind drei Calls — anlegen, ausstellen, XML oder PDF abholen — die sich mit dem seit Java 11 eingebauten HttpClient ohne zusätzliche HTTP-Bibliothek abbilden lassen.

XRechnung ist reines XML nach EN 16931, ZUGFeRD/Factur-X ein PDF mit eingebettetem XML. In Java kannst du dieses XML selbst zusammenbauen — und Codelisten, Geschäftsregeln und Profilstände jährlich nachpflegen — oder die Erzeugung an eine E-Rechnung API delegieren. Dieser Beitrag zeigt den API-Weg mit dem seit Java 11 eingebauten java.net.http.HttpClient, ganz ohne zusätzliche HTTP-Bibliothek.

Der Flow: drei Calls

  1. Anlegen — du schickst Kunde und Positionen, der Server rechnet Summen und USt.
  2. Ausstellen — die Rechnungsnummer wird atomar vergeben, das XML gerendert und gegen die EN-16931-Geschäftsregeln validiert.
  3. Abholen — das reine XRechnung-XML (CII) oder das hybride ZUGFeRD/Factur-X-PDF.

Base-URL und Test-Key (sk_test_…) kommen aus der Umgebung, damit sich der Code später ohne Änderung auf produktiv umstellen lässt — beides bekommst du mit dem Sandbox-Zugang.

Ein kleiner Client

Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;

class Fortlauf {
    private static final String BASE_URL = System.getenv("FORTLAUF_API_URL");
    private static final String API_KEY = System.getenv("FORTLAUF_API_KEY");
    private final HttpClient http = HttpClient.newHttpClient();

    private <T> HttpResponse<T> send(HttpRequest.Builder req, BodyHandler<T> handler)
            throws Exception {
        HttpResponse<T> res = http.send(
            req.header("Authorization", "Bearer " + API_KEY).build(), handler);
        if (res.statusCode() >= 400) {
            throw new IllegalStateException(
                req.build().method() + " → HTTP " + res.statusCode());
        }
        return res;
    }
}

1) Rechnung anlegen

Beträge und Mengen gehen als DECIMAL-Strings über die Leitung — nie als double, sonst holt dich die Fließkomma-Rundung ein. Intern rechnest du am besten mit java.math.BigDecimal und serialisierst als String. Der Idempotency-Key macht einen wiederholten Anlege-Call zum Replay statt zum Duplikat.

Java
import java.net.http.HttpRequest.BodyPublishers;
import java.util.UUID;

// In der Praxis serialisierst du ein DTO mit Jackson/Gson; hier als Text-Block
// zur Veranschaulichung — Beträge als Strings, nicht als Zahl-Literale.
String body = """
    {
      "currency": "EUR",
      "documentTarget": "b2g",
      "buyer": {
        "name": "Zweckverband Wasserversorgung Nordmoor",
        "addressLine1": "Deichweg 15",
        "postalCode": "26847", "city": "Nordmoor", "countryCode": "DE"
      },
      "buyerElectronicAddress": "<LEITWEG-ID>",
      "buyerReference": "<LEITWEG-ID>",
      "invoiceTypeCode": "380",
      "lineItems": [
        { "description": "Betriebsführung Leitstelle (Q3 2026)", "quantity": "3",
          "unitCode": "MON", "unitPriceNet": "2100.00", "taxRatePercent": "19" }
      ]
    }
    """;

var res = send(
    HttpRequest.newBuilder(URI.create(BASE_URL + "/invoices"))
        .header("Content-Type", "application/json")
        .header("Idempotency-Key", UUID.randomUUID().toString())
        .POST(BodyPublishers.ofString(body)),
    HttpResponse.BodyHandlers.ofString());

// draft: Status "draft", Summen serverseitig berechnet; id aus dem JSON lesen
String id = extractId(res.body()); // z. B. per Jackson: mapper.readTree(...).get("id")

buyerReference trägt die Leitweg-ID (BT-10) — nur im Behördenkontext (B2G) relevant. "documentTarget": "b2g" ist es auch, was die separate XRechnung überhaupt erzeugt. Im inländischen B2B erzeugt Fortlauf stattdessen das hybride ZUGFeRD/Factur-X, in dem BT-10 kein Pflichtfeld ist — dort kannst du das Feld weglassen oder deine eigene Bestellreferenz eintragen.

2) Ausstellen

Java
send(
    HttpRequest.newBuilder(URI.create(BASE_URL + "/invoices/" + id + "/finalize"))
        .method("PATCH", BodyPublishers.noBody()),
    HttpResponse.BodyHandlers.ofString());
// → Status "issued", Nummer vergeben, XRechnung-XML (CII) gerendert + validiert

3) XML oder PDF abholen

Java
import java.nio.file.Path;

// reines XRechnung-XML (CII)
send(
    HttpRequest.newBuilder(URI.create(BASE_URL + "/invoices/" + id + "/xml")).GET(),
    HttpResponse.BodyHandlers.ofFile(Path.of("rechnung-" + id + ".xml")));

// oder das hybride ZUGFeRD/Factur-X-PDF (PDF/A-3 mit eingebettetem XML)
send(
    HttpRequest.newBuilder(URI.create(BASE_URL + "/invoices/" + id + "/pdf")).GET(),
    HttpResponse.BodyHandlers.ofFile(Path.of("rechnung-" + id + ".pdf")));

Wie das XML technisch ins PDF kommt, zeigt ZUGFeRD-PDF/A-3 erzeugen.

Worauf es in Java ankommt

  • BigDecimal, nie double. Für Geld immer BigDecimal intern und DECIMAL-Strings auf der Leitung ("1250.00", nicht 1250.0). Der Server rechnet in ganzzahligen Minor-Units und rundet einmal; das vermeidet die Summen-Inkonsistenzen, die der Validator sonst anmahnt (KoSIT-Validierung erklärt).
  • Idempotenz im Batch/Scheduler. In einem @Scheduled-Job, mit Spring Retry oder bei Message-Redelivery kann eine Erzeugung doppelt laufen; ein stabiler Idempotency-Key pro Rechnung hält den Nummernkreis lückenlos.
  • Statuscode prüfen. HttpClient wirft bei einem 4xx/5xx nicht von selbst — der kleine Wrapper oben erzwingt, dass ein 422 aus der Validierung deinen Ablauf hart stoppt statt still durchzurutschen.
  • Kein Format selbst pflegen. Codelisten, Geschäftsregeln und der Übergang zu XRechnung 4.0 liegen hinter der API; deine Java-Integration schickt „Kunde, Positionen, Steuersätze“ und bleibt stabil.

Der gezeigte Code ist illustrativ: Die vollständige Dokumentation steht dir in der API-Referenz zur Verfügung. Base-URL und Test-Key bekommst du mit dem Sandbox-Zugang im Entwickler-Bereich. Ob du die E-Rechnung selbst baust oder einbindest, ordnet E-Rechnung im eigenen SaaS: bauen oder einbinden? ein. Dieselben drei Calls gibt es auch für PHP, Node.js und Python.

Selbst ausprobieren? Der Zugang ist kostenlos und ohne Karte — Produkt-Einstieg für Entwickler →

Oder den eigenen Abrechnungsfluss konkret durchsprechen: 30 Min, technisch, mit dem Gründer →

Häufige Fragen

Wie erstelle ich eine E-Rechnung mit Java?
Über drei HTTP-Calls gegen eine E-Rechnung API: POST zum Anlegen (der Server rechnet die Summen), PATCH zum Ausstellen (Nummer wird vergeben, XML gerendert und validiert) und GET zum Abholen des XRechnung-XML oder des ZUGFeRD-PDF. In Java genügt dafür der seit Java 11 eingebaute java.net.http.HttpClient — kein Apache HttpClient und keine eigene Schematron-Validierung nötig.
Warum kein double für Geldbeträge in Java?
Weil double Fließkomma ist und Rundungsfehler erzeugt. Nutze intern java.math.BigDecimal und schicke Beträge und Mengen als DECIMAL-Strings ("1250.00") über die Leitung. Der Server rechnet die Summen und die USt in ganzzahligen Minor-Units und rundet einmal — so entsteht keine Differenz zwischen deinem Client und dem Beleg, die der Validator sonst anmahnt.
Wie mache ich die Erzeugung in Java idempotent?
Setze beim Anlegen einen Idempotency-Key-Header mit einem pro Rechnung stabilen Wert (z. B. UUID.randomUUID() oder deine eigene Beleg-ID). Ein wiederholter POST mit demselben Key ist dann ein Replay statt eines Duplikats — wichtig bei Retries in Spring-@Retryable, in einem @Scheduled-Job oder bei Webhook-Wiederholungen.