iText HTML to PDF: Complete 2026 Conversion Guide
June 27, 2026
You usually reach for HTML-to-PDF when the document already exists in your app as markup. An invoice is an HTML template. A certificate is an HTML template. A report page with charts, tables, and branding is an HTML template too. Then someone asks for a downloadable PDF, and the quick fix turns into a mess of broken fonts, missing images, and layouts that looked fine in Chrome but fell apart in production.
That's where iText earns its keep. If you need repeatable, server-side document generation in Java or .NET, you want a PDF library that treats HTML as input to a controlled conversion pipeline, not as a browser print preview.
Why Convert HTML to PDF with iText
Browser printing is fine for one-off exports. It's bad at being an application feature. You don't control the runtime the same way, print styles tend to drift, and the output often depends on the rendering environment more than the template itself.
iText 7 changed that by shipping a dedicated HTML-to-PDF path. As noted in this Stack Overflow discussion of iText 7 html2pdf, iText 7 introduced a module built to convert HTML5 and CSS3 into standards-compliant, accessible, and searchable PDFs. That's why it keeps showing up in enterprise codebases that generate invoices, reports, certificates, and other structured documents.

What iText does better than print to PDF
The practical advantage is control.
- Template-driven output means you can render Thymeleaf, FreeMarker, Razor, or plain string templates and send the result straight into a PDF generator.
- Searchable text stays text. You're not faking a document with screenshots.
- Accessible structure is possible when the PDF needs to support screen readers or archival requirements.
- Java and C# support gives teams a consistent approach across backend stacks.
iText's pdfHTML add-on is designed for complex HTML templates, embedded fonts, external image resources, and custom data models. In real projects, that matters more than flashy demos. The primary difficulties developers face aren't with convertToPdf(). They emerge on the second week, when a customer's logo disappears because the image path was relative to a different working directory, or when a multilingual invoice inadvertently falls back to the wrong font.
Where it fits best
Use iText HTML to PDF when the document is mostly structured content:
| Best fit | Why |
|---|---|
| Invoices and statements | Tables, branding, totals, predictable pagination |
| Certificates and letters | Template-based layout, custom fonts, fixed output |
| Reports | Searchable text, charts, repeated headers, controlled margins |
| Compliance-oriented documents | Better path to accessible and archival-friendly PDFs |
If your current workflow is “open a hidden browser and hope the page prints correctly,” you're carrying rendering risk into production. A library-based pipeline is boring in the best way. It's deterministic.
If you also publish technical writeups around document workflows, this guide to PDF tone and wording is useful for tightening the language inside generated reports and handouts.
Practical rule: Treat HTML as a document template, not a webpage. The closer your markup behaves like a document from the start, the easier iText conversion becomes.
Project Setup and Basic Conversion
A basic conversion is short enough to fit in one file. That's good news, because you can get a working result quickly and then spend your time on the parts that cause problems in production.
The current major milestone for the converter is pdfHTML 3.0.0, which iText launched to improve rendering accuracy for complex CSS layouts and modern web standards, according to iText's pdfHTML 3.0.0 announcement.

Maven setup
Use your normal Maven project and add the core library plus the HTML add-on.
<dependencies>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>REPLACE_WITH_YOUR_VERSION</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>REPLACE_WITH_YOUR_VERSION</version>
</dependency>
</dependencies>
Gradle setup
If you're on Gradle, keep it just as direct.
dependencies {
implementation "com.itextpdf:itext7-core:REPLACE_WITH_YOUR_VERSION"
implementation "com.itextpdf:html2pdf:REPLACE_WITH_YOUR_VERSION"
}
I'm leaving the version placeholder intentional. iText versions move, and hardcoding one here without checking your repository or licensing constraints is how stale blog posts get copied into build files.
A quick visual walkthrough helps if you're setting this up for the first time:
<iframe width="100%" style="aspect-ratio: 16 / 9;" src="https://www.youtube.com/embed/0AWzMb-vpkk" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>Smallest useful Java example
This is the shortest version that still looks like production code.
import com.itextpdf.html2pdf.HtmlConverter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class BasicHtmlToPdf {
public static void main(String[] args) throws IOException {
String html = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Invoice</title>
<style>
body { font-family: Helvetica, Arial, sans-serif; }
h1 { color: #1e3a5f; }
.total { font-weight: bold; margin-top: 20px; }
</style>
</head>
<body>
<h1>Invoice #1001</h1>
<p>Customer: Acme Corp</p>
<p class="total">Total: $125.00</p>
</body>
</html>
""";
try (FileOutputStream output = new FileOutputStream("invoice.pdf")) {
HtmlConverter.convertToPdf(html.getBytes(StandardCharsets.UTF_8), output);
}
}
}
Same idea in C#
For .NET teams, the shape is familiar.
using System.IO;
using System.Text;
using iText.Html2pdf;
public class BasicHtmlToPdf
{
public static void Main()
{
var html = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: Helvetica, Arial, sans-serif; }
h1 { color: #1e3a5f; }
</style>
</head>
<body>
<h1>Report</h1>
<p>Generated with iText HTML to PDF.</p>
</body>
</html>
""";
using var output = new FileStream("report.pdf", FileMode.Create);
using var htmlStream = new MemoryStream(Encoding.UTF8.GetBytes(html));
HtmlConverter.ConvertToPdf(htmlStream, output);
}
}
What each part is doing
A lot of confusion comes from assuming the converter is magical. It isn't. It's predictable.
- The HTML string is your template output.
- The output stream is the final PDF destination.
- HtmlConverter parses markup and CSS, then writes the PDF.
If the basic example works but your real template doesn't, the converter usually isn't the problem. The template's resource paths, fonts, or CSS assumptions are.
That's why the next step isn't “more code.” It's handling the inputs that browsers overlook and PDF pipelines do not.
Handling CSS Fonts and Images
Here, most genuine iText HTML-to-PDF work takes place. The happy-path demo converts fine. The production template does not. A stylesheet is ignored. A custom typeface drops accented characters. Product images resolve on your laptop and vanish on the server.
Field benchmarks discussed in this pdfHTML troubleshooting article report CSS style loss in 18% of cases if styles are not inline or base URI is unset, and 22% of users cite fonts not rendering as the top production pitfall. Those numbers line up with what most backend teams eventually learn the hard way.

CSS that converts cleanly
iText handles a useful subset of HTML5 and CSS3, but you shouldn't assume browser parity. Keep your document CSS conservative.
What usually works well:
- Inline or embedded styles for document-specific rules
- Simple flex and block layouts where print behavior is predictable
- Explicit widths, padding, and borders on table-like content
- Page-aware styling instead of highly interactive web layouts
What usually causes pain:
- Highly browser-dependent CSS that leans on edge-case layout behavior
- Asset paths that only make sense in your web app
- Framework classes without the compiled stylesheet available at conversion time
A safe Java setup with external CSS looks like this:
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import java.io.FileOutputStream;
public class CssExample {
public static void main(String[] args) throws Exception {
String html = """
<html>
<head>
<link rel="stylesheet" href="styles/invoice.css">
</head>
<body>
<div class="invoice">
<h1>Invoice</h1>
<p>Styled from external CSS</p>
</div>
</body>
</html>
""";
ConverterProperties properties = new ConverterProperties();
properties.setBaseUri("src/main/resources/templates/");
try (FileOutputStream output = new FileOutputStream("styled-invoice.pdf")) {
HtmlConverter.convertToPdf(html, output, properties);
}
}
}
The important part is setBaseUri(...). Without it, relative paths for CSS, images, and some fonts often resolve badly.
Fonts need to be declared and embedded
A browser can often fall back to a local system font and hide the problem. A PDF generator can't rely on the target machine having the right typeface. If the document matters, embed the fonts you need.
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.layout.font.FontProvider;
import java.io.FileOutputStream;
public class FontEmbeddingExample {
public static void main(String[] args) throws Exception {
String html = """
<html>
<head>
<style>
body { font-family: 'NotoSans'; }
</style>
</head>
<body>
<p>Hello, multilingual PDF output.</p>
</body>
</html>
""";
FontProvider fontProvider = new FontProvider();
fontProvider.addFont("src/main/resources/fonts/NotoSans-Regular.ttf");
fontProvider.addFont("src/main/resources/fonts/NotoSans-Bold.ttf");
ConverterProperties properties = new ConverterProperties();
properties.setFontProvider(fontProvider);
try (FileOutputStream output = new FileOutputStream("font-embedded.pdf")) {
HtmlConverter.convertToPdf(html, output, properties);
}
}
}
Three practical rules help here:
- Match your CSS font-family to the embedded font. If the names drift, iText can't read your mind.
- Ship the font files with the app. Don't assume the host has them.
- Test non-ASCII characters early. Currency symbols, accented names, and regional scripts expose font issues fast.
Field note: If a PDF renders but the glyphs look wrong, the HTML usually isn't broken. The font mapping is.
Images live or die by base URI
Broken images are almost never “an iText problem.” They're a path problem.
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import java.io.FileOutputStream;
public class ImageResolutionExample {
public static void main(String[] args) throws Exception {
String html = """
<html>
<body>
<h1>Product Sheet</h1>
<img src="images/product.png" alt="Product image" width="240" />
</body>
</html>
""";
ConverterProperties properties = new ConverterProperties();
properties.setBaseUri("src/main/resources/pdf-assets/");
try (FileOutputStream output = new FileOutputStream("product-sheet.pdf")) {
HtmlConverter.convertToPdf(html, output, properties);
}
}
}
If your HTML comes from a template engine, decide on one asset root and stick to it. Don't mix web-relative URLs, filesystem-relative URLs, and CDN URLs unless you have a reason and a test case for each.
A practical checklist for production templates
Before blaming the converter, check these:
- Stylesheet loading. Is every linked CSS file reachable from the configured base URI?
- Font registration. Did you add every required TTF or OTF file to the
FontProvider? - Image paths. Do the
<img>sources resolve from the same base URI you passed intoConverterProperties? - Layout assumptions. Does the HTML rely on browser behavior that isn't appropriate for paged output?
When a template needs reliable pagination, I usually simplify the HTML rather than fight the renderer. PDF is a document medium. Clean boxes, explicit spacing, and predictable widths beat clever front-end tricks every time.
Advanced PDF Generation Techniques
Once the HTML renders correctly, the next level is controlling the PDF as a document, not just as converted content. That means page size, margins, headers, footers, page numbers, and compliance features.

Custom page size and margins
If you need A4, Letter, or a custom document size, configure the PdfDocument and Document yourself instead of relying on defaults.
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
public class PageSetupExample {
public static void main(String[] args) throws Exception {
String html = """
<html><body><h1>Monthly Report</h1><p>Content goes here.</p></body></html>
""";
PdfWriter writer = new PdfWriter("custom-page.pdf");
PdfDocument pdf = new PdfDocument(writer);
pdf.setDefaultPageSize(PageSize.A4);
Document document = new Document(pdf);
document.setMargins(36, 36, 54, 36);
HtmlConverter.convertToPdf(html, pdf, new com.itextpdf.html2pdf.ConverterProperties());
document.close();
}
}
That approach is useful when legal docs, statements, or preprinted forms need exact dimensions.
Headers and footers with page events
For repeated header and footer content, use iText's event model. It's cleaner than trying to fake every repeated element inside the HTML itself.
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
public class FooterHandler implements IEventHandler {
@Override
public void handleEvent(Event event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdf = docEvent.getDocument();
Rectangle pageSize = docEvent.getPage().getPageSize();
int pageNumber = pdf.getPageNumber(docEvent.getPage());
PdfCanvas pdfCanvas = new PdfCanvas(docEvent.getPage());
Canvas canvas = new Canvas(pdfCanvas, pageSize);
canvas.showTextAligned(
new Paragraph("Page " + pageNumber),
pageSize.getWidth() / 2,
20,
com.itextpdf.layout.properties.TextAlignment.CENTER
);
canvas.close();
}
}
Register it before conversion:
PdfDocument pdf = new PdfDocument(new PdfWriter("with-footer.pdf"));
pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new FooterHandler());
HtmlConverter.convertToPdf(html, pdf, new ConverterProperties());
pdf.close();
Keep headers and footers out of the HTML when they belong to the PDF shell. It makes pagination bugs easier to reason about.
Converting to iText elements first
Sometimes you want more control than direct conversion gives you. In that case, convert the HTML into iText elements and add them manually.
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.IElement;
import java.util.List;
List<IElement> elements = HtmlConverter.convertToElements(html);
for (IElement element : elements) {
if (element instanceof com.itextpdf.layout.element.IBlockElement block) {
document.add(block);
}
}
That's useful when you need to inject a cover page, prepend a summary block, or interleave HTML-derived content with native iText tables and paragraphs.
Accessibility and form-related considerations
iText's HTML conversion path is also attractive when accessibility matters. The library is built to support standards-compliant, searchable PDFs and can participate in workflows that target formats like PDF/A, as noted earlier in the article. If your organization has archival or screen-reader requirements, treat that as a design constraint from day one. It's much easier to keep semantic HTML clean than to retrofit structure after a PDF has already been generated.
HTML forms are a separate decision. Some teams expect browser form markup to become a polished interactive PDF form automatically. In practice, review the result carefully and decide whether a real AcroForm workflow is a better fit for that document.
Performance Memory and Licensing
The code that works on a developer machine often struggles under queue-based batch generation. Large HTML payloads, heavy image assets, and unnecessary in-memory transformations add pressure fast. The fix usually isn't exotic. Keep the pipeline simple, reuse known-good template structures, and stream where you can.
Performance habits that hold up
A few habits make iText HTML to PDF much easier to run in production:
- Prefer streams over giant intermediate strings when the HTML is large or generated from files.
- Keep templates document-oriented. Highly nested markup and frontend-heavy class systems make rendering harder to reason about.
- Resolve fonts and image roots explicitly so you don't waste time on fallback behavior and retries.
- Benchmark representative templates. A single receipt, a 30-page report, and a multilingual statement stress different parts of the pipeline.
If you're generating many PDFs concurrently, isolate expensive resources, keep asset loading predictable, and avoid per-request surprises such as scanning arbitrary font directories.
Memory advice without overengineering
I've had the best results when treating conversion as a bounded document job. Load only the resources that template needs. Avoid embedding huge images at original web dimensions when the PDF will display them smaller anyway. If the application stores reusable assets like logos, normalize them once instead of reprocessing them every request.
Production PDF generation fails slowly. It starts with a few slow templates, then queue times stretch, then memory spikes show up under load.
AGPL and commercial licensing
iText uses a dual-licensing model. That isn't a footnote. It's part of the implementation decision.
Here's the practical comparison:
| Feature | AGPLv3 License | Commercial License |
|---|---|---|
| Cost model | Open-source license terms apply | Paid commercial terms apply |
| Source code obligations | Typically appropriate when your own distribution and usage comply with AGPL requirements | Intended for teams that need to keep their code proprietary |
| Closed-source projects | Usually a poor fit | Common fit |
| Vendor support path | Community and public resources | Commercial relationship and product licensing |
| Risk profile for businesses | Requires legal review | Requires procurement review |
This isn't legal advice. It's implementation advice. If your company ships proprietary software, stop and get a real licensing decision before iText becomes embedded in your document pipeline. Developers often discover this too late, after templates, tests, and operational dependencies are already in place.
Troubleshooting Common Issues and Alternatives
Most iText HTML-to-PDF problems fall into a small set of buckets. The library is usually doing exactly what you told it to do. The issue is that the HTML, CSS, fonts, or resources weren't prepared for a PDF renderer.
Fast diagnosis list
-
Fonts render as fallback characters
Register the font files with aFontProvider, then make sure the CSSfont-familymatches what you embedded. -
Images are missing
Set a correctBaseUri. Relative paths in<img src>and linked CSS won't resolve reliably without it. -
Styles don't apply
Check whether the stylesheet is reachable from the same base URI and whether the CSS relies on browser-specific behavior that doesn't translate well to paged output. -
Tables break across pages awkwardly
Simplify the table markup and test print-oriented CSS. Web layouts that look elegant on a scrolling page often need stricter widths and spacing in PDF. -
Template data appears blank
Earlier field testing referenced in the verified background notes mentions mismatches between template variables and the data model as a recurring source of broken output. In practice, log the rendered HTML before conversion and inspect it directly.
One .NET expectation to drop early
If you're working with iTextSharp in C#, don't assume embedding a live HTML file as a PDF attachment will behave like a normal clickable in-document web experience. A Microsoft Q&A thread on embedding HTML in PDF with iTextSharp discusses that limitation directly. Attachments and hyperlinks are not the same thing in the PDF model.
When iText isn't the right tool
iText is strong when the output is a structured document. It's not always the best answer for pixel-capturing a modern web app screen.
Choose an alternative when:
- You need browser-perfect rendering of a live app view. Tools built on headless Chromium can be better for that.
- You want screenshot-style fidelity more than semantic PDF structure.
- Your page depends heavily on client-side JavaScript before it becomes printable.
Choose iText when the document itself is the product. That includes invoices, statements, certificates, letters, reports, and archival-friendly outputs. In those cases, document-first HTML plus iText usually beats browser automation for maintainability.
If you publish technical content from rough AI drafts, HumanizeAIText is a practical cleanup step before you hit publish. It rewrites stiff, machine-sounding copy into more natural prose while preserving the facts, structure, and intent, which is useful for developer blogs, documentation, client deliverables, and marketing content that needs to sound like a real person wrote it.