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:
| Profile | Structured content | French reform status |
| MINIMUM | Header amounts only | Booking aid, not a full e-invoice |
| BASIC WL | Header level, no lines | Booking aid, not a full e-invoice |
| BASIC | Full invoice with simple lines | Qualifies as a structured e-invoice |
| EN 16931 (Comfort) | Complete EN 16931 semantic model | Qualifies; the recommended default |
| EXTENDED | EN 16931 plus industry extensions | Qualifies, 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.
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
| Operation | Endpoint | Input | Output |
| Create Factur-X | POST /v1/create/facturx | JSON | PDF/A-3 binary |
| PDF to Factur-X (AI) | POST /v1/transform/to/facturx | PDF, image, DOCX, XLSX | PDF/A-3 binary |
| Validate Factur-X | POST /v1/validate/facturx | Factur-X PDF | Validation JSON |
| Extract as JSON | POST /v1/extract/json | Factur-X PDF | Structured JSON |
| Extract CII XML | POST /v1/extract/xml | Factur-X PDF | CII XML |
| Render CII as PDF | POST /v1/render/cii/to/pdf | CII XML | PDF 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.