# HTML to PDF in Java HTML to PDF in Java and Spring Boot with OpenHTMLtoPDF, iText pdfHTML, Playwright, or a plain HttpClient API call. Working code included. **Quick answer.** Post JSON to `https://html-pdf.com/v1/pdf` with a Bearer key and save the response bytes. The JDK's `java.net.http.HttpClient` is enough. ```bash curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" \ -d '{"html": "

Hello

"}' -o hello.pdf ``` Java has mature HTML to PDF libraries, and they fall into two camps. Pure-Java renderers are easy to deploy and handle print-style documents well, but they don't support modern CSS. Browser-based tools render exactly like Chrome, but you have to run Chrome. This guide covers both. Then it shows how to skip the browser with one HTTP call, including a Spring Boot controller. ## Which Java HTML to PDF option? This table compares the main options by engine, CSS support and license. | Tool | Renders with | Flexbox / grid | JavaScript | License | |---|---|---|---|---| | OpenHTMLtoPDF | Own Java engine (Flying Saucer fork) + PDFBox | No (CSS 2.1 plus some paged media) | No | LGPL | | Flying Saucer | Own Java engine | No | No | LGPL | | iText pdfHTML | iText's own engine | Partial | No | AGPL or commercial | | Playwright for Java | Headless Chromium | Yes | Yes | Apache 2.0 | | html-pdf.com API | Headless Chromium, hosted | Yes | Yes | Paid per page | ## Option 1: OpenHTMLtoPDF (or Flying Saucer) OpenHTMLtoPDF is the easiest pure-Java route for simple documents. It's a fork of Flying Saucer that writes PDFs with Apache PDFBox. The original project is quiet, and an active community fork publishes under the `io.github.openhtmltopdf` group ID. It needs well-formed XHTML, so close every tag and escape `&` as `&`. ```xml io.github.openhtmltopdf openhtmltopdf-pdfbox ``` ```java import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import java.io.FileOutputStream; import java.io.OutputStream; public class OpenHtmlExample { public static void main(String[] args) throws Exception { String html = """

Invoice #042

Design work$120.00
"""; try (OutputStream os = new FileOutputStream("invoice.pdf")) { PdfRendererBuilder builder = new PdfRendererBuilder(); builder.useFastMode(); builder.withHtmlContent(html, null); // base URI for relative images builder.toStream(os); builder.run(); } } } ``` It is a good fit for table-based invoices and reports. Layouts built with flexbox or grid will not render as designed, and you register custom fonts yourself. Flying Saucer works the same way with `ITextRenderer`. ## Option 2: iText pdfHTML iText gives you a one-line conversion plus advanced PDF features. Its pdfHTML add-on converts HTML with a single call and has strong PDF features (PDF/A, signing, forms) through iText Core. ```java import com.itextpdf.html2pdf.HtmlConverter; import java.io.FileOutputStream; HtmlConverter.convertToPdf("

Invoice #042

", new FileOutputStream("invoice.pdf")); ``` **Check the license first.** iText and pdfHTML are dual-licensed under the AGPL or a commercial license. Using them under the AGPL in a network service generally means making your own source code available under the AGPL too. Otherwise you need a paid license. ## Option 3: Playwright for Java (headless Chromium) This option gives you exact Chrome rendering, so it's the one to use when your templates rely on modern CSS. Playwright for Java downloads its own Chromium build. ```xml com.microsoft.playwright playwright ``` ```java import com.microsoft.playwright.*; import com.microsoft.playwright.options.Margin; import java.nio.file.Paths; try (Playwright playwright = Playwright.create(); Browser browser = playwright.chromium().launch()) { Page page = browser.newPage(); page.setContent("

Invoice #042

"); page.pdf(new Page.PdfOptions() .setPath(Paths.get("invoice.pdf")) .setFormat("A4") .setPrintBackground(true) .setMargin(new Margin().setTop("15mm").setBottom("15mm"))); } ``` Output matches Chrome's print preview. The trade-off is operations. Your server image grows by a few hundred MB with Chromium and its system libraries, and each render uses a browser process and its memory. You also have to pool browsers, set timeouts and keep Chromium patched. ## Or skip the server: one HTTP call This section gives you Playwright-quality output with no browser to run. html-pdf.com runs headless Chromium for you. You send HTML and get PDF bytes back, using only the JDK's built-in `java.net.http.HttpClient`. Get a key, then open the link we email you to confirm your address. That adds 100 free credits. ```bash curl -X POST https://html-pdf.com/v1/signup -H "Content-Type: application/json" -d '{"email": "you@example.com"}' ``` ```java import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.*; import java.nio.file.*; import java.time.Duration; import java.util.Map; public class HtmlPdf { private static final HttpClient CLIENT = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)).build(); private static final ObjectMapper JSON = new ObjectMapper(); public static byte[] render(String html) throws Exception { String body = JSON.writeValueAsString(Map.of( "html", html, "format", "A4", "margin", "15mm", "footer_html", "
" + "Page of
")); HttpRequest request = HttpRequest.newBuilder(URI.create("https://html-pdf.com/v1/pdf")) .timeout(Duration.ofSeconds(60)) .header("Authorization", "Bearer " + System.getenv("HTML_PDF_KEY")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() != 200) { // {"error": {"code": "...", "message": "...", "hint": "..."}} throw new IllegalStateException(new String(response.body())); } System.out.println("Pages: " + response.headers().firstValue("X-Pages").orElse("?")); return response.body(); } public static void main(String[] args) throws Exception { Files.write(Path.of("invoice.pdf"), render("

Invoice #042

")); } } ``` To convert a public web page, send `"url": "https://example.com"` instead of `html`. Add `"response": "url"` to get JSON with a download link valid for 24 hours. ## Spring Boot: return a PDF from a controller Here is how to serve a PDF from a Spring Boot endpoint. Render a Thymeleaf template to a string, send it, and return the bytes with the right headers. This reuses `HtmlPdf.render` from above. ```java import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; @RestController public class InvoicePdfController { private final TemplateEngine templates; private final InvoiceRepository invoices; public InvoicePdfController(TemplateEngine templates, InvoiceRepository invoices) { this.templates = templates; this.invoices = invoices; } @GetMapping("/invoices/{id}/pdf") public ResponseEntity pdf(@PathVariable long id) throws Exception { Context ctx = new Context(); ctx.setVariable("invoice", invoices.findById(id).orElseThrow()); String html = templates.process("invoice-pdf", ctx); byte[] pdf = HtmlPdf.render(html); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_PDF) .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.inline().filename("invoice-" + id + ".pdf").build().toString()) .body(pdf); } } ``` Use absolute `https://` URLs for images and stylesheets in the template, or inline them. The renderer can't reach `localhost` or your private network. You can also send the HTML and assets together as multipart (`index.html` plus files, with an `options` JSON field). ## Costs and limits Pricing is per page, with no subscription. 1 credit = 1 PDF page, and you get 100 free credits. After that, packs start at $10 for 1,000 pages, purchased credits never expire, and failed renders are free. Set `"max_pages": 20` to fail a runaway document without being charged. A `402 insufficient_credits` response means you need to top up. - **Limits:** 20 MB request, 30 seconds per render, 120 requests per minute. More: [page breaks](https://html-pdf.com/html-to-pdf-page-breaks) · [headers, footers and page numbers](https://html-pdf.com/pdf-headers-footers-page-numbers) · [API docs](https://html-pdf.com/docs) · [sign in](https://html-pdf.com/login) ## FAQ ### How do I convert HTML to PDF in Java? Use OpenHTMLtoPDF for simple XHTML documents, Playwright for Java when you need modern CSS, or post JSON to `https://html-pdf.com/v1/pdf` with `java.net.http.HttpClient` and save the bytes. ### Is there a free HTML to PDF library for Java? Yes. OpenHTMLtoPDF and Flying Saucer are LGPL, and Playwright for Java is Apache 2.0. iText pdfHTML is AGPL, so commercial network services usually need a paid license. ### Does OpenHTMLtoPDF support flexbox and grid? No. It supports CSS 2.1 plus some paged media, so flexbox and grid layouts need a Chromium-based tool like Playwright or the html-pdf.com API. ### Do credits expire? No. Purchased credits never expire, 1 credit is 1 PDF page, and failed renders cost nothing.