How to Create a ZUGFeRD PDF with Java
This tutorial shows how to create a ZUGFeRD or Factur-X hybrid PDF/A-3 invoice with Java. It uses the pdfRest Create ZUGFeRD PDF API Tool to send canonical invoice XML and a visual invoice PDF, then returns a managed invoice resource for the next step in the workflow.
Why Create a ZUGFeRD PDF with Java?
Electronic invoicing programs increasingly require an invoice that people can read and systems can process. ZUGFeRD and Factur-X pair a visual PDF with structured invoice XML so finance teams, customers, and downstream systems can work from the same invoice record.
A Java billing service could generate the invoice XML from its accounting data, pair it with a customer-facing PDF, and submit both to pdfRest before delivery. That gives the service a documented API step for producing the hybrid invoice rather than relying on a manual desktop process.
The supplied PDF is retained when it agrees with the canonical XML. With regenerate_pdf=true, the service can create a replacement PDF when it cannot confirm that agreement or finds a mismatch, which gives the workflow a defined fallback instead of silently accepting an uncertain visual invoice.
What the Request Does
The multipart request sends the invoice XML as file and the visual invoice PDF as pdf_file. regenerate_pdf controls the fallback behavior. Optional render_options set locale, label language, fonts, and accent color only for a replacement PDF; they do not alter a supplied PDF that is preserved.
The Java samples use OkHttp for HTTP and the repository’s established project dependencies. Applications adopting the class should provide equivalent dependencies and obtain credentials from secure runtime configuration.
How to Create a ZUGFeRD PDF with Java Code Example
import io.github.cdimascio.dotenv.Dotenv;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONObject;
public class ZugferdPdf {
// By default, we use the US-based API service. This is the primary endpoint for global use.
private static final String API_URL = "https://api.pdfrest.com";
// For GDPR compliance and enhanced performance for European users, use the EU-based service
// instead.
// For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work
// private static final String API_URL = "https://eu-api.pdfrest.com";
// Specify your API key here, or in the environment variable PDFREST_API_KEY.
// You can also put the environment variable in a .env file.
private static final String DEFAULT_API_KEY = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
private static final OkHttpClient CLIENT =
new OkHttpClient.Builder().readTimeout(60, TimeUnit.SECONDS).build();
public static void main(String[] args) throws IOException {
// Specify XML and PDF paths here, or as the first and second program arguments.
File invoiceXml = new File(args.length > 0 ? args[0] : "/path/to/invoice.xml");
File invoicePdf = new File(args.length > 1 ? args[1] : "/path/to/invoice.pdf");
String apiKey =
Dotenv.configure()
.ignoreIfMalformed()
.ignoreIfMissing()
.load()
.get("PDFREST_API_KEY", DEFAULT_API_KEY);
JSONObject options =
new JSONObject()
.put("locale", "de-DE")
.put("label_language", "de")
.put("font", "arial")
.put("bold_font", "arialbold")
.put("accent_color_rgb", new int[] {0, 92, 171});
// Create a ZUGFeRD / Factur-X PDF/A-3 invoice from XML and an existing invoice PDF.
// pdfRest preserves the supplied PDF when it agrees with the canonical XML.
// Fallback generation handles a mismatch or an unconfirmed PDF/XML match.
// The render options style only that replacement PDF, not a preserved supplied PDF.
MultipartBody body =
new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(
"file",
invoiceXml.getName(),
RequestBody.create(invoiceXml, MediaType.parse("application/xml")))
.addFormDataPart(
"pdf_file",
invoicePdf.getName(),
RequestBody.create(invoicePdf, MediaType.parse("application/pdf")))
.addFormDataPart("regenerate_pdf", "true")
.addFormDataPart("render_options", options.toString())
.addFormDataPart("output", "zugferd_invoice")
.build();
send(
new Request.Builder()
.url(API_URL + "/zugferd-pdf")
.header("Api-Key", apiKey)
.post(body)
.build());
}
private static void send(Request request) throws IOException {
try (Response response = CLIENT.newCall(request).execute()) {
String body = response.body() == null ? "" : response.body().string();
System.out.println("Result code " + response.code());
System.out.println(body);
if (!response.isSuccessful()) {
System.exit(1);
}
}
}
}
Source: View the multipart sample on GitHub.
Breaking Down the Code
The request is compact, but each field has a distinct role in the hybrid-invoice workflow.
- Identify the two document roles. The
filefield holds canonical invoice XML, whilepdf_fileholds the customer-facing invoice PDF. The sample marks them asapplication/xmlandapplication/pdf, respectively, so the service can distinguish the structured record from its visual companion. - Build the multipart request.
MultipartBody.Buildercreates the form and its boundary.RequestBody.createassigns the XML and PDF media types before the request is sent with OkHttp. - Configure the request safely. The sample loads
PDFREST_API_KEYthrough dotenv, with a placeholder only as a fallback. Replace the placeholder and keep real credentials outside source control. - Understand the fallback settings.
regenerate_pdf=truepermits a replacement PDF when pdfRest cannot confirm that the supplied PDF agrees with the XML. Therender_optionsobject controls locale, labels, fonts, and accent color for that replacement only; it does not restyle a supplied PDF that pdfRest preserves. - Name and handle the result.
output=zugferd_invoicegives the generated resource a useful output name. Thesendmethod reads and prints the JSON body, then exits nonzero whenresponse.isSuccessful()is false. A production caller can parse a successful body after that check.
Beyond the Tutorial
In this Java tutorial, you created a ZUGFeRD or Factur-X hybrid invoice from invoice XML and a visual PDF. The response gives the application a managed output resource it can download, deliver, or pass to a following pdfRest operation.
For the complete request fields, accepted values, response contract, and service limits, review the Create ZUGFeRD PDF API Tool documentation. The same endpoint can be used with multipart uploads when the files are present in the current request or with resource IDs after a separate upload step.
The repository also includes a JSON-payload Create ZUGFeRD PDF sample for Java. That version uploads the source files first and then sends their resource IDs to /zugferd-pdf, which is useful when a service already stages input files for later operations.