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, turning legacy PDF invoices into compliant documents, and reading incoming files into your ERP. 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, converting, and extracting, 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. Credit where due: for a Java shop determined to keep everything local, it is a legitimate choice.

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.


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

Validation matters twice in a French pipeline: before your outgoing invoices hit a PDP's acceptance checks, and when supplier documents arrive claiming to be Factur-X. 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 →


Convert PDF invoices to Factur-X

Plenty of French businesses will reach September 2026 with an invoicing system that produces ordinary PDFs. The transform endpoint upgrades those documents in place: AI extraction reads the invoice fields (scans and photos included), builds the CII XML, and returns a conformant Factur-X hybrid without you touching the original rendering pipeline.

static byte[] pdfToFacturx(Path source) throws IOException, InterruptedException {
    String boundary = "----" + UUID.randomUUID();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(BASE_URL + "/v1/transform/to/facturx"))
        .header("Authorization", "Bearer " + API_KEY)
        .header("Content-Type", "multipart/form-data; boundary=" + boundary)
        .POST(MultipartUpload.of(source, boundary, Map.of("profile", "en16931")))
        .build();

    HttpResponse<byte[]> response = HTTP.send(request, HttpResponse.BodyHandlers.ofByteArray());
    if (response.statusCode() != 200) {
        throw new IllegalStateException(new String(response.body()));
    }
    return response.body();
}

Files.write(Path.of("fournisseur-facturx.pdf"),
    pdfToFacturx(Path.of("fournisseur-original.pdf")));

The profile field selects any rung of the profile ladder (minimum through extended); leave it at en16931 for reform-grade output. Input formats beyond PDF include JPEG, PNG, TIFF, WEBP, HEIC, DOCX, and XLSX. No open-source Java library offers this operation, because it requires document understanding rather than XML plumbing.


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 decision framework for French projects:

A local library earns its place when the deployment cannot make outbound calls, invoice volume lives entirely inside data you already structure, and the team accepts curating the dependency stack and every Factur-X release migration themselves. Mustangproject has proven itself in exactly those conditions.

The API earns its place when compliance outcomes matter more than infrastructure ownership: current FNFE-MPE artifacts applied server-side the day they take effect, PDF/A-3 output that holds up under PDP acceptance checks whether the input was JSON or a legacy PDF, BR-FR findings your support team can forward to a customer, and the same integration extending to ZUGFeRD, XRechnung, and Peppol UBL when your market does.

The hybrid setup is underrated: keep local generation if you have it, and gate every outgoing document through /v1/validate/facturx. The validator does not care which library produced the file.

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
PDF to Factur-X (AI)POST /v1/transform/to/facturxPDF, image, DOCX, XLSXPDF/A-3 binary
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