Automation MCP Server Features Blog Pricing Contact
Integration Factur-X Format

Create and Validate Factur-X Invoices in Java: Complete Guide

Everything a Java team needs for the French e-invoicing reform: generating Factur-X hybrid PDFs from JSON, validating them against the official EN 16931 and Factur-X profile rules, and reading incoming files into your ERP, with structured extraction for hybrids and AI parsing for plain supplier PDFs. Built on java.net.http with no SDK.

On September 1, 2026, electronic invoicing stops being optional in France. Every company subject to French VAT must be able to receive structured e-invoices from that date, large companies and ETIs must start issuing them, and SMEs follow a year later. Factur-X is the format at the center of that reform for most issuers, because it is the only core format that stays readable by a human while carrying machine-readable data. For Java teams behind ERP backends, billing services, and AP automation, the practical question is what it takes to emit and check compliant Factur-X without absorbing the PDF/A-3 and Schematron problem into the codebase.

A note on naming before anything else: Factur-X and Germany's ZUGFeRD 2.x are one and the same technical standard, CII XML embedded in a PDF/A-3, published jointly by FNFE-MPE and FeRD under two national brands. This guide takes the French perspective; the German-market equivalent, with the B2B mandate timeline and German sample data, is the companion guide to ZUGFeRD in Java.

The plan here: a short tour of the reform and the Factur-X profile ladder, an honest look at Mustangproject and the local-library route, then working Java code for the four operations that matter, creating, validating, extracting, and AI parsing, against the Factur-X API. Everything runs on java.net.http.HttpClient (Java 17+, no SDK) and every snippet has a runnable counterpart in the Java examples repository.

The French e-invoicing reform

The reform reshapes how domestic B2B invoices travel, not just what they look like:

Deadlines. September 1, 2026: reception becomes mandatory for all French businesses, and issuance becomes mandatory for grandes entreprises and ETIs. September 1, 2027: issuance extends to PME and micro-entreprises. Alongside e-invoicing, an e-reporting obligation covers B2C and cross-border transactions on the same calendar.

Delivery model. Invoices do not travel by email under the reform. They are exchanged through registered Plateformes de Dématérialisation Partenaires (PDPs), which validate documents, route them to the recipient's platform via the central directory, and report tax data to the administration. Since the state scaled the public portal back to the directory and data-concentrator role in 2024, every business needs a PDP connection; your invoices must survive a PDP's format checks, which is why validation belongs in your pipeline before submission.

B2G stays on Chorus Pro. Invoicing the French public sector has run through Chorus Pro since the 2017-2020 phase-in, and Factur-X is an accepted upload format there. One generation pipeline can therefore serve both your B2B and public-sector customers.

Accepted formats. The reform's core set ("socle") is Factur-X, UBL, and CII. Factur-X is the common choice for issuers whose customers still expect a PDF they can open, which in practice is most of them.


Factur-X profiles and versions

Factur-X is maintained by FNFE-MPE (Forum National de la Facture Electronique et des Marchés Publics Electroniques) together with the German FeRD, and the two publish in lockstep: Factur-X 1.0.7 corresponds to ZUGFeRD 2.3. The version numbers differ, the artifacts are shared.

The standard defines a ladder of five profiles, each declaring how much structured data the XML carries:

ProfileStructured contentFrench reform status
MINIMUMHeader amounts onlyBooking aid, not a full e-invoice
BASIC WLHeader level, no linesBooking aid, not a full e-invoice
BASICFull invoice with simple linesQualifies as a structured e-invoice
EN 16931 (Comfort)Complete EN 16931 semantic modelQualifies; the recommended default
EXTENDEDEN 16931 plus industry extensionsQualifies, for complex scenarios

For French B2B invoicing, generate EN 16931 unless a trading partner explicitly requires something else. That is what /v1/create/facturx produces, and the profile a document declares in its BT-24 identifier is exactly the rule set it gets validated against.


The Factur-X Java library landscape

Search for a Factur-X Java library and you will land on Mustangproject, the Apache-2.0 project that has co-evolved with the standard for years and serves as its de-facto reference implementation on the JVM. It can build a hybrid PDF from its invoice object model, read the XML back out, and run validation, and since Saxon-HE is a native Java library, Schematron works in-process on the JVM in a way it simply does not in most other ecosystems. It is the strongest local option in any ecosystem, which is exactly why its operating costs are worth spelling out.

What the first prototype will not show you is the operating cost. The library sits on PDFBox, Saxon, and veraPDF, and those dependencies' upgrade and CVE cycles become yours. When FNFE-MPE ships a new Factur-X release, picking up the new schemas and rule sets is your migration project. Converting an invoice PDF that already exists (from a reporting engine or an upstream system) into genuinely conformant PDF/A-3 remains the sharpest edge, since font embedding and color-space defects surface only when a PDP rejects the file. And reading data out of an ordinary, non-hybrid supplier PDF is out of scope entirely; the library needs structured input.

The sections below take the other route: the compliance machinery runs server-side behind a REST API, and your Java code is reduced to HTTP calls with the client the JDK already ships.

The full comparison and migration guide: Mustangproject alternatives for ZUGFeRD and Factur-X teams →


Client setup

Two constants and one client instance are the whole setup. Jackson appears later for JSON parsing; nothing else is required.

Create a free InvoiceXML account to get an API key with 100 free credits, no credit card needed. Load the key from the environment or a secrets manager rather than hard-coding it:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

static final String BASE_URL = "https://api.invoicexml.com";
static final String API_KEY = System.getenv("INVOICEXML_API_KEY");

static final HttpClient HTTP = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(10))
    .build();

Create Factur-X invoices in Java

A Java 17 text block holds the request body. Note the French specifics: the intra-community VAT number (N° TVA, FR + 2-digit key + SIREN), the SIREN carried in legalRegistration with the SIRENE scheme code 0002, and mixed VAT rates (20% standard, 5.5% reduced) across the lines. Totals and the per-rate VAT breakdown are computed for you.

import java.nio.file.Files;
import java.nio.file.Path;

String invoiceJson = """
    {
      "invoice": {
        "invoiceNumber": "FA-2026-0042",
        "issueDate": "2026-09-01",
        "currency": "EUR",
        "seller": {
          "name": "Atelier Numerique SARL",
          "vatIdentifier": "FR32123456789",
          "legalRegistration": { "identifier": "123456789", "schemeId": "0002" },
          "postalAddress": {
            "line1": "18 Rue de la Republique",
            "city": "Lyon",
            "postCode": "69002",
            "country": "FR"
          }
        },
        "buyer": {
          "name": "Librairie Grand Siecle SAS",
          "postalAddress": {
            "line1": "5 Boulevard Saint-Germain",
            "city": "Paris",
            "postCode": "75005",
            "country": "FR"
          }
        },
        "paymentDetails": {
          "paymentAccountIdentifier": "FR7630006000011234567890189"
        },
        "lines": [
          {
            "quantity": 12,
            "item": { "name": "Maintenance applicative" },
            "priceDetails": { "netPrice": 180.00 },
            "vatInformation": { "rate": 20 }
          },
          {
            "quantity": 200,
            "item": { "name": "Guide utilisateur imprime" },
            "priceDetails": { "netPrice": 4.50 },
            "vatInformation": { "rate": 5.5 }
          }
        ]
      }
    }
    """;

HttpRequest create = HttpRequest.newBuilder()
    .uri(URI.create(BASE_URL + "/v1/create/facturx"))
    .header("Authorization", "Bearer " + API_KEY)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(invoiceJson))
    .build();

HttpResponse<byte[]> created = HTTP.send(create, HttpResponse.BodyHandlers.ofByteArray());
if (created.statusCode() != 200) {
    throw new IllegalStateException(new String(created.body()));
}
Files.write(Path.of("facture-facturx.pdf"), created.body());

The response is a finished Factur-X EN 16931 document: PDF/A-3 container, embedded factur-x.xml, XMP metadata declaring the profile, already checked against the EN 16931 Schematron. A request whose data would produce a non-compliant invoice (say, a VAT breakdown that does not reconcile) is refused with HTTP 400 and the violated rules listed as findings, which means nothing invalid ever reaches a customer or a PDP.

Runnable create example on GitHub →


Validate Factur-X invoices in Java

Invoices from /v1/create/facturx clear the official rules before the API returns them, so they meet a PDP's acceptance checks as they are. Validation matters for everything else in a French pipeline: supplier documents arriving that claim to be Factur-X, and files produced by systems you did not build. The endpoint reads the embedded XML, detects the declared profile, and runs the matching official rules, including the French BR-FR constraints.

File uploads need multipart encoding, which java.net.http does not provide out of the box. A compact helper covers every upload endpoint in this guide:

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;

final class MultipartUpload {
    static HttpRequest.BodyPublisher of(Path file, String boundary,
            Map<String, String> formFields) throws IOException {
        var chunks = new ArrayList<byte[]>();
        formFields.forEach((name, value) -> chunks.add(
            ("--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + name
                + "\"\r\n\r\n" + value + "\r\n").getBytes(StandardCharsets.UTF_8)));
        chunks.add(("--" + boundary
            + "\r\nContent-Disposition: form-data; name=\"file\"; filename=\""
            + file.getFileName() + "\"\r\nContent-Type: application/octet-stream\r\n\r\n")
            .getBytes(StandardCharsets.UTF_8));
        chunks.add(Files.readAllBytes(file));
        chunks.add(("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));
        return HttpRequest.BodyPublishers.ofByteArrays(chunks);
    }
}

Map the JSON verdict onto records with Jackson and the call becomes:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
record FacturxReport(boolean valid, String detail, ReportData data,
        List<Finding> errors, List<Finding> warnings) {}

@JsonIgnoreProperties(ignoreUnknown = true)
record ReportData(Boolean schemaValid, Boolean schematronValid, String profile) {}

@JsonIgnoreProperties(ignoreUnknown = true)
record Finding(String rule, String message, List<String> fields) {}

static FacturxReport validateFacturx(Path pdf) throws IOException, InterruptedException {
    String boundary = "----" + UUID.randomUUID();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(BASE_URL + "/v1/validate/facturx"))
        .header("Authorization", "Bearer " + API_KEY)
        .header("Content-Type", "multipart/form-data; boundary=" + boundary)
        .POST(MultipartUpload.of(pdf, boundary, Map.of()))
        .build();

    HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
    return new ObjectMapper().readValue(response.body(), FacturxReport.class);
}

FacturxReport report = validateFacturx(Path.of("facture-facturx.pdf"));
if (report.valid()) {
    System.out.println("Compliant Factur-X, profile " + report.data().profile());
} else {
    report.errors().forEach(f ->
        System.out.printf("[%s] %s (fields: %s)%n", f.rule(), f.message(), f.fields()));
}

A finished validation always answers HTTP 200; the pass or fail verdict lives in valid, with non-2xx statuses reserved for transport problems such as a bad API key or an unreadable upload. Because the findings carry field paths, an AP workflow can point a supplier at the exact value that failed. Wire this into your CI as well: a JUnit test that validates a freshly generated invoice catches regressions in your invoice data mapping long before a PDP does.

Runnable validation example on GitHub →


Parse supplier PDFs with AI

The reform obliges you to receive e-invoices, but nothing obliges every supplier to send one on day one, so ordinary PDFs will keep arriving. POST /v1/parse/json turns those into data: the AI pipeline runs OCR and semantic field extraction over typed, scanned, and photographed invoices and answers with the same normalized JSON the extract endpoint below produces for genuine Factur-X, plus a confidence object (overall and four area scores between 0.0 and 1.0) that Jackson maps onto a record for threshold-based routing:

@JsonIgnoreProperties(ignoreUnknown = true)
record ParseConfidence(double overall, double sellerIdentification,
        double buyerIdentification, double taxCalculation, double lineItems) {}

@JsonIgnoreProperties(ignoreUnknown = true)
record ParsedInvoice(JsonNode invoice, ParseConfidence confidence) {}

static ParsedInvoice parseSupplierPdf(Path pdf) throws IOException, InterruptedException {
    String boundary = "----" + UUID.randomUUID();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(BASE_URL + "/v1/parse/json"))
        .header("Authorization", "Bearer " + API_KEY)
        .header("Content-Type", "multipart/form-data; boundary=" + boundary)
        .POST(MultipartUpload.of(pdf, boundary, Map.of()))
        .build();

    HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 200) {
        throw new IllegalStateException(response.body());
    }
    return new ObjectMapper().readValue(response.body(), ParsedInvoice.class);
}

ParsedInvoice parsed = parseSupplierPdf(Path.of("fournisseur-scan.pdf"));
if (parsed.confidence().overall() >= 0.7) {
    bookInvoice(parsed.invoice());
} else {
    reviewQueue.add(parsed);
}

Non-invoices are rejected with error code 4008 and multi-invoice files with 4009, so the endpoint filters junk before your booking logic ever runs. The envelope mirrors the /v1/create request body, and for e-invoices carrying embedded supporting documents (the EN 16931 BG-24 group), /v1/extract/attachments returns the original files as a ZIP; see the attachment extraction reference.


Extract Factur-X data as JSON

On the receiving side, incoming Factur-X files carry their own structured data, and the extract endpoint hands it to you parsed and normalized, ready for your ERP or accounting import:

import com.fasterxml.jackson.databind.JsonNode;

static JsonNode readIncoming(Path pdf) throws IOException, InterruptedException {
    String boundary = "----" + UUID.randomUUID();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(BASE_URL + "/v1/extract/json"))
        .header("Authorization", "Bearer " + API_KEY)
        .header("Content-Type", "multipart/form-data; boundary=" + boundary)
        .POST(MultipartUpload.of(pdf, boundary, Map.of()))
        .build();

    HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
    return new ObjectMapper().readTree(response.body());
}

JsonNode facture = readIncoming(Path.of("fournisseur-facturx.pdf"));
System.out.println("Facture " + facture.get("invoiceNumber").asText()
    + " de " + facture.at("/seller/name").asText());

Prefer the raw embedded XML? /v1/extract/xml returns the CII document as application/xml for JAXB or StAX processing.


Spring Boot integration

In a Spring Boot 3.2+ service, bind the connection settings with @ConfigurationProperties and expose the operations through a bean built on RestClient:

@ConfigurationProperties(prefix = "invoicexml")
public record InvoiceXmlProperties(String baseUrl, String apiKey) {}

@Service
public class FacturxService {

    private final RestClient rest;

    public FacturxService(InvoiceXmlProperties props) {
        this.rest = RestClient.builder()
            .baseUrl(props.baseUrl())
            .defaultHeader("Authorization", "Bearer " + props.apiKey())
            .build();
    }

    public byte[] createFacturx(Object invoiceData) {
        return rest.post()
            .uri("/v1/create/facturx")
            .contentType(MediaType.APPLICATION_JSON)
            .body(Map.of("invoice", invoiceData))
            .retrieve()
            .body(byte[].class);
    }

    public FacturxReport validateFacturx(byte[] pdf) {
        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
        form.add("file", new ByteArrayResource(pdf) {
            @Override
            public String getFilename() { return "facture.pdf"; }
        });
        return rest.post()
            .uri("/v1/validate/facturx")
            .contentType(MediaType.MULTIPART_FORM_DATA)
            .body(form)
            .retrieve()
            .body(FacturxReport.class);
    }
}

Spring supplies the multipart converter, so the hand-rolled body builder stays in the plain-Java examples. Add Resilience4j retry around the calls for production, retrying 5xx and I/O failures only; a 400 carries validation findings you want to read, not retry.


Factur-X Java library vs REST API

A fair comparison for French projects:

The library route is a standing engineering commitment. Mustangproject is free and proven, and adopting it means curating the PDFBox, Saxon, and veraPDF dependency stack, owning PDF/A-3 conformance, and treating every Factur-X release as your own migration project: watch the FNFE-MPE announcement, wait for the library to catch up, bump the version, retest, and redeploy every service that issues invoices. With the September 2026 reform live and evolving, that cycle repeats on the government's schedule, not yours.

The API is a complete compliance service. Current FNFE-MPE artifacts applied server-side the day they take effect, with nothing to monitor, no artifact updates, and no redeploys on your side. PDF/A-3 output that holds up under PDP acceptance checks, BR-FR findings your support team can forward to a customer verbatim, deep e-invoicing expertise and professional support behind the integration, missing features integrated on request, and the same integration extending to ZUGFeRD, XRechnung, and Peppol UBL when your market does. Your codebase keeps one HTTP client and zero compliance dependencies.

The deciding question is who owns the moving target. Compliance changes on the regulator's calendar. Build on a library and that calendar drives your releases indefinitely; build on the API and your integration is finished the day it works, because staying compliant is our job.

Data handling is strict throughout: requests are processed in memory and discarded when the response ships. No storage, no logging of document content, no training on your data. SIREN numbers, IBANs, and customer relationships exist server-side only for the duration of one HTTP exchange.


Endpoint reference

OperationEndpointInputOutput
Create Factur-XPOST /v1/create/facturxJSONPDF/A-3 binary
Parse PDF with AIPOST /v1/parse/jsonInvoice PDF (typed or scanned)Structured JSON + confidence
Extract attachmentsPOST /v1/extract/attachmentsFactur-X PDF or CII/UBL XMLZIP archive
Validate Factur-XPOST /v1/validate/facturxFactur-X PDFValidation JSON
Extract as JSONPOST /v1/extract/jsonFactur-X PDFStructured JSON
Extract CII XMLPOST /v1/extract/xmlFactur-X PDFCII XML
Render CII as PDFPOST /v1/render/cii/to/pdfCII XMLPDF binary

Full OpenAPI 3.1 schema: api.invoicexml.com/v1/openapi  |  Interactive API explorer: api.invoicexml.com/v1/scalar


Get started

The runnable versions of these snippets live on GitHub; export INVOICEXML_API_KEY and they work as-is:

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 Factur-X, ZUGFeRD, XRechnung, 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.

Start free today

Ready to automate your invoices?

Validate, convert and embed compliant e-invoices through one API. Start your 30-day free trial. No credit card required.

GDPR Compliant No credit card required Setup in minutes
Peppol UBL
Factur-X
EN 16931
142 / 142 passed
Compliant
PDF/A-3 embedded