Generating a PDF from HTML in Java comes down to four options: openhtmltopdf for clean XHTML and CSS 2.1 layouts (the most maintained pure-Java choice), Flying Saucer for legacy code in the same lineage, Playwright for Java when you need full Chromium fidelity including flexbox, grid, and JavaScript, and a hosted API like PDF4.dev when you do not want to run a browser or PDF engine yourself. For most invoices, reports, and certificates built from server-rendered HTML, openhtmltopdf is the default answer. When the template depends on modern CSS or scripts, render with Chromium.
The trap most teams hit: they build an HTML template in the browser using flexbox and grid, then plug it into a pure-Java renderer and the layout collapses. openhtmltopdf is not a browser. It implements CSS 2.1 and a slice of CSS 3, with no JavaScript. Knowing that up front decides which option you pick.
Which Java HTML-to-PDF option should you use?
The choice depends on three things: how modern your CSS is, whether the page needs JavaScript, and how much infrastructure you want to own. The table below maps each option against those criteria so you can pick before writing code.
| Option | CSS support | JavaScript | License | Infra weight | Best for |
|---|---|---|---|---|---|
| openhtmltopdf | CSS 2.1 + partial CSS 3, no flexbox/grid | None | LGPL | Light (one JAR) | Invoices, reports, certificates from XHTML |
| Flying Saucer | CSS 2.1 | None | LGPL | Light (one JAR) | Legacy projects already using it |
| iText pdfHTML | Good CSS via add-on | None | AGPL or commercial | Light (JARs) | Teams already licensed for iText |
| OpenPDF | Programmatic only, no HTML module | None | LGPL/MPL | Light (one JAR) | Drawing PDFs by hand, not from HTML |
| Playwright for Java | Full modern CSS | Full | Apache 2.0 | Heavy (Chromium binary) | Pixel-accurate pages, charts, dashboards |
| PDF4.dev API | Full modern CSS (Chromium) | Full | Hosted (no install) | None (HTTP call) | Shipping PDFs without running a browser |
"Pure-Java" means the renderer runs entirely on the JVM with no external process. openhtmltopdf, Flying Saucer, iText, and OpenPDF are pure-Java. Playwright for Java drives a separate Chromium binary, and PDF4.dev runs Chromium on its own servers.
The fastest way to decide: if your HTML is valid XHTML with CSS 2.1 styling and no scripts, use openhtmltopdf. If it relies on flexbox, grid, web components, or JavaScript charts, use a Chromium renderer (Playwright for Java self-hosted, or PDF4.dev hosted). Everything else is a detail.
How do you generate a PDF with openhtmltopdf?
openhtmltopdf converts well-formed XHTML into a vector PDF with selectable text using a PdfRendererBuilder. It is the maintained successor to Flying Saucer, with a PDFBox backend, font registration, and SVG support. Add the dependency, point the builder at your XHTML and an output stream, and call run().
The key constraint: the input must be valid XHTML, not arbitrary HTML5. Unclosed tags like <br> or <img> without a closing slash will throw a parse error. If your HTML comes from a template engine, make sure it emits XML-compliant markup.
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<version>1.0.10</version>
</dependency>A minimal renderer that turns an XHTML string into a PDF file:
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class HtmlToPdf {
public static void main(String[] args) throws Exception {
String xhtml = """
<html>
<head>
<style>
body { font-family: 'Inter', sans-serif; color: #111827; }
h1 { font-size: 24px; }
</style>
</head>
<body>
<h1>Invoice INV-001</h1>
<p>Total due: 1,500.00 EUR</p>
</body>
</html>
""";
try (OutputStream os = Files.newOutputStream(Path.of("invoice.pdf"))) {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useFastMode();
// baseUri resolves relative image and CSS paths
builder.withHtmlContent(xhtml, "file:///app/templates/");
builder.toStream(os);
builder.run();
}
}
}Honest caveats: openhtmltopdf has no flexbox, no grid, and no JavaScript. It targets CSS 2.1 plus a partial CSS 3 (it does support position, floats, tables, and @page rules for headers and footers). If you register no font, it falls back to the built-in PDF base fonts, which means no non-Latin scripts and no emoji. Register a TTF for anything beyond basic Latin.
How do you load custom fonts and images in openhtmltopdf?
Register every font file explicitly with useFont and resolve images through the baseUri or a custom data URI. openhtmltopdf does not read system fonts, so a CSS font-family: 'Roboto' rule does nothing unless you have called useFont with the matching family name. This is the single most common reason text renders in the wrong typeface.
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import java.io.File;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class FontsExample {
public static void main(String[] args) throws Exception {
String xhtml = "<html><body style='font-family: Inter'>"
+ "<h1>Quarterly report</h1></body></html>";
try (OutputStream os = Files.newOutputStream(Path.of("report.pdf"))) {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useFastMode();
// family name must match the CSS font-family value
builder.useFont(new File("/app/fonts/Inter-Regular.ttf"), "Inter");
builder.useFont(new File("/app/fonts/Inter-Bold.ttf"), "Inter",
700, PdfRendererBuilder.FontStyle.NORMAL, true);
builder.withHtmlContent(xhtml, "file:///app/templates/");
builder.toStream(os);
builder.run();
}
}
}For repeating headers and footers, use CSS paged-media rules. openhtmltopdf honours @page margins and position: running(...) boxes, so you can pin a logo to the top of every page without manual page math. Images load from the baseUri, from absolute file:// URLs, or from base64 data URIs embedded directly in the markup, which avoids any filesystem path issues in containers.
When should you use Flying Saucer instead of openhtmltopdf?
Use Flying Saucer only when you maintain an existing codebase already built on it. Flying Saucer (the org.xhtmlrenderer packages) is the original CSS 2.1 renderer that openhtmltopdf forked from. It still works, but it receives little maintenance, lacks the newer PDFBox backend, and has weaker font and SVG handling. For any new project, openhtmltopdf is the same idea with active development and security patches.
The API is nearly identical, which makes migration cheap. Flying Saucer's classic entry point is ITextRenderer:
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class FlyingSaucerExample {
public static void main(String[] args) throws Exception {
String xhtml = "<html><body><h1>Legacy report</h1></body></html>";
try (OutputStream os = Files.newOutputStream(Path.of("legacy.pdf"))) {
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(xhtml);
renderer.layout();
renderer.createPDF(os);
}
}
}The shared limitations carry over: no JavaScript, no flexbox or grid, XHTML required. Because openhtmltopdf and Flying Saucer share their layout engine, a template that renders correctly in one renders the same in the other. If you are already on Flying Saucer and hit a font or security issue, porting to openhtmltopdf is usually a dependency swap plus changing ITextRenderer to PdfRendererBuilder.
Can iText or OpenPDF convert HTML to PDF?
iText converts HTML through its separate pdfHTML add-on, licensed under AGPL or a paid commercial license; OpenPDF has no maintained HTML-to-PDF module and is meant for drawing PDFs programmatically. If you want HTML-driven layouts and you are not already an iText licensee, openhtmltopdf is the simpler, LGPL choice.
iText itself is a low-level PDF construction library: you place text, lines, and tables by coordinate. That is the right tool when you need exact control over a fixed form, but it is the wrong tool when your source of truth is an HTML template. The pdfHTML add-on layers HTML parsing on top, and its CSS coverage is good, but the AGPL license means your own application must also be open source unless you buy a commercial license.
The AGPL is a strong copyleft license. If you embed iText 7 or pdfHTML in a server application that is reachable over a network, AGPL section 13 requires you to offer your application's source to its users unless you hold a commercial license. Check this with your legal team before shipping iText in a closed-source product.
OpenPDF is the LGPL/MPL fork of iText 4 (the last LGPL iText version). It is fine for programmatic PDF drawing under a permissive license, but it does not ship a current HTML renderer. If you reach for OpenPDF expecting html2pdf-style conversion, you will not find it. For HTML input, route to openhtmltopdf or a Chromium-based renderer instead.
How do you generate a PDF with Playwright for Java?
When the HTML needs full browser fidelity (flexbox, grid, web fonts, JavaScript charts), drive headless Chromium with Playwright for Java and call page.pdf(). Playwright runs the same engine that renders the page in Chrome, so what you see in the browser is what lands in the PDF. The cost is weight: Playwright downloads a Chromium binary (roughly 150 MB) and runs a separate process.
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.49.0</version>
</dependency>The first run needs mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install chromium" (or the equivalent) to download the browser. Honest caveats: in serverless or locked-down containers, shipping and launching Chromium is the hard part. You need the right shared libraries (libnss3, libatk, fonts), enough memory (Chromium can use 200+ MB per page), and a base image that allows process spawning. Many teams move to a hosted renderer specifically to avoid maintaining this. The fidelity is excellent; the operational tax is real.
How do you generate a PDF from HTML with the PDF4.dev API in Java?
Call POST https://pdf4.dev/api/v1/render with java.net.http.HttpClient, send your HTML in the JSON body, and PDF4.dev renders it with headless Chromium on the server and returns the PDF. You ship no browser, no fonts, and no PDF engine in your own deployment. This gives you full Chromium fidelity (flexbox, grid, JavaScript, web fonts) without the Playwright operational tax.
The body accepts either raw html or a stored template_id with Handlebars {{variables}}, a data object, an optional format, and a delivery mode. Use delivery: "url" to get back a signed link instead of a base64 blob, which keeps large PDFs out of your application memory.
curl -X POST https://pdf4.dev/api/v1/render \
-H "Authorization: Bearer p4_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Invoice INV-001</h1><p>Total: 1,500.00 EUR</p>",
"data": {},
"delivery": "url"
}'In production, parse the JSON response with Jackson or Gson rather than a regex. Honest framing: PDF4.dev is a paid hosted service and adds a network hop, so a self-hosted openhtmltopdf call is lower latency for simple XHTML. The trade is operational: you offload Chromium, fonts, and scaling. If you want to validate a layout before wiring the API, the free HTML to PDF toolTry it free renders the same way in the browser, and Webpage to PDFTry it free captures a live URL.
Which option should you choose?
Pick by what your HTML needs and how much infrastructure you want to own. The summary below maps common scenarios to a recommendation.
| Scenario | Recommended option |
|---|---|
| Invoice, receipt, or report from server-rendered XHTML, CSS 2.1 only | openhtmltopdf |
| Existing project already on Flying Saucer | Flying Saucer (or port to openhtmltopdf) |
| Already licensed for iText, need its CSS features | iText pdfHTML |
| Drawing a fixed-layout form by coordinate, no HTML | OpenPDF or iText |
| Template uses flexbox, grid, charts, or JavaScript, self-hosted | Playwright for Java |
| Same modern HTML, but you do not want to run Chromium | PDF4.dev API |
| Spring Boot service that must stay lightweight | openhtmltopdf for simple layouts, PDF4.dev for modern CSS |
The decision tree is short. Is your HTML valid XHTML with CSS 2.1 and no scripts? Use openhtmltopdf, the lightest and most maintained pure-Java path. Does it depend on modern CSS or JavaScript? Choose between Playwright for Java (you run Chromium) and PDF4.dev (Chromium runs hosted). Are you drawing a fixed form rather than converting HTML? That is iText or OpenPDF territory, not an HTML renderer at all.
For a Spring Boot service specifically, the cleanest setup is Thymeleaf to produce XHTML, then openhtmltopdf to render it, streamed back from a controller. When a template outgrows CSS 2.1, swap that one render call for an HTTP call to PDF4.dev without changing the rest of your pipeline. See the Spring Boot PDF generation guide for the full controller wiring.
Common errors and how to fix them
Three errors cause most of the time lost when generating PDFs from HTML in Java. Each has a direct fix tied to the renderer's constraints.
org.xml.sax.SAXParseException in openhtmltopdf. The input is not valid XHTML. A self-closing tag is missing its slash (<br> instead of <br/>) or an attribute value is unquoted. Run the HTML through a cleaner like jsoup with Document.outputSettings().syntax(Syntax.xml) before passing it to PdfRendererBuilder, or fix the template to emit XML-compliant markup.
Wrong font or boxes instead of glyphs. openhtmltopdf used a base font because no matching useFont call registered the family in your CSS. Register the exact TTF or OTF for every font-family you reference. For non-Latin scripts or emoji, you must supply a font that contains those glyphs; the PDF base fonts do not.
Chromium fails to launch with Playwright in a container. The base image is missing shared libraries or fonts. Install libnss3, libatk1.0-0, libgbm1, and a font package, or move to a renderer that runs Chromium elsewhere. A hosted API such as PDF4.dev removes this class of failure because the browser runs on its servers, not in your container. For a deeper walkthrough of HTML-to-PDF tradeoffs across stacks, see the complete HTML to PDF guide, and for the equivalent in another ecosystem, the Python HTML to PDF guide.
The short version: openhtmltopdf for clean XHTML, Chromium (Playwright for Java or PDF4.dev) for modern HTML, iText or OpenPDF only when you are drawing PDFs by hand. Match the renderer to the markup and the rest is wiring.
Free tools mentioned:
Start generating PDFs
Build PDF templates with a visual editor. Render them via API from any language in ~300ms.



