Skip to main content

Apache PDFBox Java Tutorial: How to Generate PDFs

· 13 min read

Apache PDFBox Java Tutorial: Generate PDFs from HTML with Code Examples

When it comes to generating PDFs in Java, many developers reach for HTML to PDF libraries that rely on browser engines to render content. However, if you need full control over layout, fonts, positioning, and structure – especially for business-critical documents like invoices, reports, or certificates – Apache PDFBox offers a flexible, code-driven solution. Rather than converting entire web pages, PDFBox enables you to programmatically construct PDF documents from the ground up using Java. In this guide, you'll learn how to use PDFBox's low-level API to create precisely formatted PDFs. With this approach, you skip browser dependencies entirely and gain complete authority over how your PDFs are styled, structured, and generated.

What is Apache PDFBox?

Apache PDFBox is a Java library developed by the Apache Software Foundation for working directly with PDF documents. Rather than rendering HTML or relying on a browser engine, PDFBox implements the PDF specification in pure Java, allowing you to:

  • Create new PDF documents programmatically from scratch.
  • Modify existing documents (add text, images, annotations).
  • Extract content (text, images, metadata).
  • Sign and encrypt PDFs.
  • Split PDFs into separate files or merge multiple documents.
Open Source License

Apache PDFBox is distributed under the Apache License 2.0, so you can integrate it freely into both commercial and open-source projects without worrying about licensing fees.

Why Use Apache PDFBox for Programmatic PDF Generation?

Although PDFBox doesn't support direct HTML to PDF conversion, it excels when you need:

  • Precise layout control – exact positioning of text, images and graphics.
  • Efficient performance – low memory footprint and rapid output.
  • Advanced document features – multi-page reports with headers, footers and numbering.
  • Custom logic – data-driven or conditional content generation.
  • Enterprise integration – seamless Java EE/Spring support, no external engines.
  • Built-in security – encryption, password protection and digital signatures.

Understanding PDF Generation with PDFBox

PDFBox doesn't convert HTML directly to PDF like browser-based solutions. It's a PDF creation library that lets you build PDF documents programmatically using Java code.

Approach 1: Create PDFs from scratch:

  • Write Java code to directly add text, images, and formatting.
  • No HTML input needed – pure programmatic control.
  • Shown in: SimplePDFCreator.java example.

Approach 2: Parse HTML content, then create PDFs:

  • Parse HTML using JSoup to understand document structure.
  • Extract content (text, headings, tables) and basic styling information.
  • Translate HTML elements into PDF drawing commands using PDFBox APIs.
  • Control layout and positioning manually in your Java code.
  • Shown in: HTMLtoPDFExample.java example.
No Direct HTML Support

PDFBox itself has no knowledge of HTML or CSS. You must write Java code to interpret HTML elements and translate them into PDF drawing commands. This approach gives you complete control over the final output but requires significantly more development effort than libraries that directly convert HTML to PDF.

Project Setup

Step 1: Development Environment Setup

Ensure your development environment is properly configured for PDF generation with PDFBox.

RequirementRecommendation & Resources
JDKJDK 8 or higher. Download from Eclipse Adoptium or Oracle JDK.
Build SystemMaven or Gradle for dependency management. Install Maven or Gradle.
IDEModern IDE with Java support like IntelliJ IDEA or Eclipse.

Step 2: Maven Dependencies Configuration

Create a new Maven project and add the required dependencies.

Add the following dependencies to your pom.xml:

<!-- Apache PDFBox for PDF creation -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.5</version>
</dependency>

<!-- JSoup for HTML parsing -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.20.1</version>
</dependency>

Dependency Explanations:

  • pdfbox: Core PDFBox library for PDF document creation and manipulation.
  • jsoup: HTML parser for extracting content and structure from HTML strings.

Simple PDF Creation Example

This example creates a basic PDF with two lines of text. Run it to understand PDFBox fundamentals before moving to HTML conversion.

SimplePDFCreator.java:

package com.example;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;

import java.io.IOException;

public class SimplePDFCreator {

public static void main(String[] args) {
try {
createSimplePDF();
System.out.println("PDF created successfully!");
} catch (IOException e) {
System.err.println("Error creating PDF: " + e.getMessage());
}
}

public static void createSimplePDF() throws IOException {
// Create a new document
try (PDDocument document = new PDDocument()) {

// Create a new page
PDPage page = new PDPage();
document.addPage(page);

// Create content stream for writing
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {

// Set font and size
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 18);

// Begin text block
contentStream.beginText();

// Set position (x=50, y=700 from bottom-left)
contentStream.newLineAtOffset(50, 700);

// Write text
contentStream.showText("Hello! Nice to see you here.");

// Move to next line
contentStream.newLineAtOffset(0, -30);
contentStream.showText("This document was created using Apache PDFBox.");

// End text block
contentStream.endText();
}

// Save the document
document.save("simple-example.pdf");
}
}
}

Basic HTML Parsing Example

Create this class in src/main/java/com/example/HTMLtoPDFExample.java.

HTMLtoPDFExample.java
package com.example;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.awt.Color;
import java.io.IOException;

public class HTMLtoPDFExample {

public static void main(String[] args) {
// Sample HTML content with invoice data
String htmlContent = """
<!DOCTYPE html>
<html>
<head>
<title>Sample Invoice</title>
</head>
<body>
<h1>Invoice</h1>
<h2>Invoice Number: #404</h2>
<h2>To: Very Important Client</h2>
<h3>Warning: Excessive services included</h3>
<p>Date: <span class="date">2025-05-23</span></p>
<table>
<tr>
<th>Item</th>
<th>Price</th>
</tr>
<tr>
<td>Web Development</td>
<td>$1,200.00</td>
</tr>
<tr>
<td>SEO Optimization</td>
<td>$800.00</td>
</tr>
<tr>
<td>Mobile App Design</td>
<td>$1,500.00</td>
</tr>
<tr>
<td>Database Setup</td>
<td>$600.00</td>
</tr>
</table>
</body>
</html>
""";

try {
// Convert HTML content to PDF and save to file
convertHTMLtoPDF(htmlContent, "parsed-invoice.pdf");
System.out.println("HTML successfully converted to PDF: parsed-invoice.pdf");
} catch (IOException e) {
System.err.println("Error converting HTML to PDF: " + e.getMessage());
}
}

public static void convertHTMLtoPDF(String htmlContent, String outputPath) throws IOException {
// Parse HTML string into a structured Document object using JSoup
Document doc = Jsoup.parse(htmlContent);

// Create new PDF document using try-with-resources
try (PDDocument pdfDocument = new PDDocument()) {
// Create a new page and add it to the document
PDPage page = new PDPage();
pdfDocument.addPage(page);

// Create content stream for drawing on the page
try (PDPageContentStream contentStream = new PDPageContentStream(pdfDocument, page)) {
float yPosition = 730f; // Start from top of page (PDF coordinates start from bottom-left)
float margin = 50f; // Left margin for all content

// Process and render different HTML elements in order
yPosition = addHeaders(contentStream, doc, yPosition, margin);
yPosition = addParagraphs(contentStream, doc, yPosition, margin);
yPosition = addTables(contentStream, doc, yPosition, margin);
}

// Save the completed PDF document to specified path
pdfDocument.save(outputPath);
}
}

private static float addHeaders(PDPageContentStream contentStream, Document doc,
float yPosition, float margin) throws IOException {
// Select all header elements
Elements headers = doc.select("h1, h2, h3");

// Process each header element individually
for (Element header : headers) {
// Set different font sizes
float fontSize = switch (header.tagName()) {
case "h1" -> 24f;
case "h2" -> 16f;
case "h3" -> 14f;
default -> 12f;
};

// Set text colors
Color headerColor = switch (header.tagName()) {
case "h1" -> Color.BLACK;
case "h2" -> Color.DARK_GRAY;
case "h3" -> Color.GRAY;
default -> Color.BLACK;
};

// Apply bold font, size, and color settings
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), fontSize);
contentStream.setNonStrokingColor(headerColor);

// Begin text rendering block
contentStream.beginText();
contentStream.newLineAtOffset(margin, yPosition); // Position text at margin
contentStream.showText(header.text()); // Render the header text
contentStream.endText();

// Move position down based on font size plus spacing
yPosition -= (fontSize + 15f);
}

return yPosition - 10f; // Add extra space after all headers
}

private static float addParagraphs(PDPageContentStream contentStream, Document doc,
float yPosition, float margin) throws IOException {
// Select all paragraph elements from the HTML
Elements paragraphs = doc.select("p");

// Set standard font for paragraph text
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12f);
contentStream.setNonStrokingColor(Color.BLACK);

// Render each paragraph
for (Element paragraph : paragraphs) {
contentStream.beginText();
contentStream.newLineAtOffset(margin, yPosition);
contentStream.showText(paragraph.text()); // Extract and render text content
contentStream.endText();

// Move down for next paragraph
yPosition -= 20f;
}

// Add spacing after paragraphs
return yPosition - 10f;
}

private static float addTables(PDPageContentStream contentStream, Document doc,
float yPosition, float margin) throws IOException {
// Find all table elements in the HTML document
Elements tables = doc.select("table");

// Process each table separately
for (Element table : tables) {
yPosition = renderTable(contentStream, table, yPosition, margin);
}

return yPosition;
}

private static float renderTable(PDPageContentStream contentStream, Element table,
float yPosition, float margin) throws IOException {
// Get all table rows (both header and data rows)
Elements rows = table.select("tr");
if (rows.isEmpty()) return yPosition;

// Calculate column layout
int columnCount = rows.get(0).select("td, th").size();
float columnWidth = 200f; // Fixed width per column
float tableWidth = columnWidth * columnCount; // Total table width

// Set standard font for table content
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 11f);

// Process each table row
for (Element row : rows) {
Elements cells = row.select("td, th"); // Get all cells in current row
float currentX = margin; // Start at left margin
boolean isHeader = !cells.select("th").isEmpty();

// Draw background for header rows
if (isHeader) {
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
contentStream.addRect(margin, yPosition - 15, tableWidth, 20); // Create rectangle
contentStream.fill();
}

// Reset text color for cell content
contentStream.setNonStrokingColor(Color.BLACK);

// Process each cell in the current row
for (Element cell : cells) {
// Use bold font for header cells and regular for data cells
if (isHeader) {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), 11f);
} else {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 11f);
}

// Render cell text content
contentStream.beginText();
contentStream.newLineAtOffset(currentX + 5, yPosition - 10);
contentStream.showText(cell.text()); // Extract and display cell text
contentStream.endText();

// Draw gray border around cell
contentStream.setStrokingColor(Color.GRAY);
contentStream.setLineWidth(0.5f);
contentStream.addRect(currentX, yPosition - 15, columnWidth, 20);
contentStream.stroke();

// Move to next column position
currentX += columnWidth;
}

// Move down for next row
yPosition -= 25f;
}

return yPosition;
}
}

This example demonstrates the core concept of converting HTML to PDF using Apache PDFBox and JSoup.

How It Works:

  1. Parse HTML – JSoup reads HTML and creates a structured document tree.
  2. Create PDF – PDFBox creates a blank PDF page with coordinate system.
  3. Render Elements – Convert each HTML element (headers, paragraphs, tables) to PDF content.
  4. Position Content – Track vertical position as content flows down the page.

Key Concept:

  • HTML provides structure and content.
  • Java code controls exact positioning, fonts, colors, and layout.
  • Each element type gets custom rendering logic.

Advanced PDFBox Features for Professional Documents

Document Security and Encryption

Protect your PDF documents with password encryption and access restrictions to control who can view, edit, or print your content.

Click to see complete security example
package com.example;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;

import java.io.IOException;

public class SecurePDFCreator {

public static void main(String[] args) {
try {
createSecurePDF();
System.out.println("Secure PDF created successfully!");
} catch (IOException e) {
System.err.println("Error creating secure PDF: " + e.getMessage());
}
}

public static void createSecurePDF() throws IOException {
// Create a new document
try (PDDocument document = new PDDocument()) {

// Create a new page
PDPage page = new PDPage();
document.addPage(page);

// Add content to the page
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 16);
contentStream.beginText();
contentStream.newLineAtOffset(50, 700);
contentStream.showText("This is a password-protected PDF document!");
contentStream.endText();
}

// Set up document protection
String userPassword = "user123"; // Password to open the document
String ownerPassword = "owner456"; // Password for full permissions

// Create access permissions
AccessPermission permissions = new AccessPermission();
permissions.setCanPrint(false); // Disable printing
permissions.setCanModify(false); // Disable editing
permissions.setCanExtractContent(false); // Disable copying text

// Create protection policy
StandardProtectionPolicy protectionPolicy = new StandardProtectionPolicy(
ownerPassword, userPassword, permissions);
protectionPolicy.setEncryptionKeyLength(128);

// Apply protection to document
document.protect(protectionPolicy);

// Save the protected document
document.save("secure-document.pdf");
}
}
}

Custom Fonts and Typography

Enhance your PDF documents with custom fonts to match your brand identity and improve visual appeal beyond standard system fonts.

Click to see complete custom fonts example
package com.example;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;

import java.awt.Color;
import java.io.IOException;

public class CustomFontExample {

public static void main(String[] args) {
try {
createCustomFontPDF();
System.out.println("Custom font PDF created successfully!");
} catch (IOException e) {
System.err.println("Error creating PDF with custom fonts: " + e.getMessage());
}
}

public static void createCustomFontPDF() throws IOException {
// Create a new document
try (PDDocument document = new PDDocument()) {

// Create a new page
PDPage page = new PDPage();
document.addPage(page);

// Create content stream for writing
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
float yPosition = 720f;
float margin = 50f;

// Try to load custom font
try {
// Load custom font from resources
PDType0Font averiaLibreFont = PDType0Font.load(document,
CustomFontExample.class.getResourceAsStream("/fonts/AveriaLibre-Regular.ttf"));

contentStream.setFont(averiaLibreFont, 24);
contentStream.setNonStrokingColor(Color.BLUE);
contentStream.beginText();
contentStream.newLineAtOffset(margin, yPosition);
contentStream.showText("Custom font added - Averia Libre");
contentStream.endText();
yPosition -= 40;

// Different weight of same font family
PDType0Font averiaLibreBold = PDType0Font.load(document,
CustomFontExample.class.getResourceAsStream("/fonts/AveriaLibre-Bold.ttf"));

contentStream.setFont(averiaLibreBold, 24);
contentStream.setNonStrokingColor(Color.DARK_GRAY);
contentStream.beginText();
contentStream.newLineAtOffset(margin, yPosition);
contentStream.showText("Custom font - Averia Libre Bold Version");
contentStream.endText();
yPosition -= 50;

} catch (Exception e) {
System.err.println("Custom font not found - using standard fonts only");
}

// Standard fonts comparison
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 18);
contentStream.setNonStrokingColor(Color.BLACK);
contentStream.beginText();
contentStream.newLineAtOffset(margin, yPosition);
contentStream.showText("Standard Helvetica Font");
contentStream.endText();
yPosition -= 30;

contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN), 18);
contentStream.beginText();
contentStream.newLineAtOffset(margin, yPosition);
contentStream.showText("Standard Times Roman Font");
contentStream.endText();
yPosition -= 30;

contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.COURIER), 18);
contentStream.beginText();
contentStream.newLineAtOffset(margin, yPosition);
contentStream.showText("Standard Courier Font");
contentStream.endText();
yPosition -= 50;
}

// Save the document
document.save("custom-fonts-example.pdf");
}
}
}

Font Options:

Standard Fonts (built-in):

PDFBox includes 14 standard fonts that work immediately without downloading:

  • Helvetica, Times-Roman, Courier (regular, bold, italic variants).
  • Symbol and ZapfDingbats for special characters.
  • Perfect for professional documents and guaranteed compatibility

Custom Fonts (download required):

  1. Download fonts from Google Fonts (free) or other font providers.
  2. Place .ttf or .otf files in src/main/resources/fonts/ directory.
  3. Load using PDType0Font.load().
tip

Always include fallback to standard fonts if custom fonts fail to load.


Here's the preview of output:

Custom Fonts and Typography - PDFBox

PDF Merging and Splitting

Combine multiple PDF files into one document or split large PDFs into individual pages for easier management and distribution.

Click to see complete PDF merge example
package com.example;

import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;

import java.io.IOException;

public class PDFMergeExample {

public static void main(String[] args) {
try {
// Create sample PDFs and merge them
createSamplePDFs();
mergePDFDocuments();

System.out.println("PDF merge completed successfully!");
} catch (IOException e) {
System.err.println("Error during PDF merge: " + e.getMessage());
}
}

// Create simple sample PDFs
private static void createSamplePDFs() throws IOException {
// Create Document 1
try (PDDocument doc1 = new PDDocument()) {
PDPage page = new PDPage();
doc1.addPage(page);

try (PDPageContentStream contentStream = new PDPageContentStream(doc1, page)) {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 20);
contentStream.beginText();
contentStream.newLineAtOffset(50, 700);
contentStream.showText("This is Document 1");
contentStream.endText();
}

doc1.save("document1.pdf");
}

// Create Document 2
try (PDDocument doc2 = new PDDocument()) {
PDPage page = new PDPage();
doc2.addPage(page);

try (PDPageContentStream contentStream = new PDPageContentStream(doc2, page)) {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 20);
contentStream.beginText();
contentStream.newLineAtOffset(50, 700);
contentStream.showText("This is Document 2");
contentStream.endText();
}

doc2.save("document2.pdf");
}

// Create Document 3
try (PDDocument doc3 = new PDDocument()) {
PDPage page = new PDPage();
doc3.addPage(page);

try (PDPageContentStream contentStream = new PDPageContentStream(doc3, page)) {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 20);
contentStream.beginText();
contentStream.newLineAtOffset(50, 700);
contentStream.showText("This is Document 3");
contentStream.endText();
}

doc3.save("document3.pdf");
}

System.out.println("Sample PDFs created successfully");
}

// Merge PDFs using PDFBox
private static void mergePDFDocuments() throws IOException {
// Create merger utility
PDFMergerUtility mergerUtility = new PDFMergerUtility();

// Add source documents in order
mergerUtility.addSource("document1.pdf");
mergerUtility.addSource("document2.pdf");
mergerUtility.addSource("document3.pdf");

// Set destination filename
mergerUtility.setDestinationFileName("merged-document.pdf");

// Merge documents
mergerUtility.mergeDocuments(IOUtils.createMemoryOnlyStreamCache());

System.out.println("PDFs merged successfully!");
}
}

Adding Images and Graphics

Enhance your PDF documents with images, logos, watermarks, and graphic elements to create professional and visually appealing documents.

Click to see complete images example
package com.example;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;

import java.io.IOException;
import java.io.InputStream;

public class AddImageExample {

public static void main(String[] args) {
try {
createPDFWithImage();
System.out.println("PDF with image created successfully!");
} catch (IOException e) {
System.err.println("Error creating PDF: " + e.getMessage());
}
}

public static void createPDFWithImage() throws IOException {
// Create a new document
try (PDDocument document = new PDDocument()) {

// Create a new page
PDPage page = new PDPage();
document.addPage(page);

// Create content stream
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {

// Add title
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), 20);
contentStream.beginText();
contentStream.newLineAtOffset(50, 700);
contentStream.showText("PDF with Image Example");
contentStream.endText();

// Try to add image
try {
// Load image from resources (place image in src/main/resources/images/)
InputStream imageStream = AddImageExample.class.getResourceAsStream("/images/image.jpg");

if (imageStream != null) {
PDImageXObject image = PDImageXObject.createFromByteArray(
document,
imageStream.readAllBytes(),
"image"
);

// Draw image below the title
contentStream.drawImage(image, 50, 400, 400, 250);
imageStream.close();
}

} catch (Exception e) {
System.err.println("Error loading image: " + e.getMessage());
}
}

// Save the document
document.save("pdf-with-image.pdf");
}
}
}

Image Integration:

  • Supported formats: PNG, JPEG, GIF, BMP, TIFF.
  • Positioning: Specify exact x, y coordinates and dimensions (width, height).
  • Resource loading: Place images in src/main/resources/images/ folder.
  • Update the path in code: /images/image.jpg.
Performance Tip

For best results, use images sized appropriately for your PDF. Large images (>2MB) may slow down PDF generation and increase file size significantly.


Here's the preview of output:

Adding Images and Graphics - PDFBox - Java

Alternative Approaches for PDF Generation in Java

While Apache PDFBox offers excellent programmatic control for PDFs, you may want to consider these alternatives based on your specific needs:

SolutionDescriptionLearn More
Playwright• Modern browser rendering with JavaScript and CSS3 support.
• Best for converting existing web pages with full web standards.
How to Convert HTML to PDF in Java Using Playwright
Flying Saucer• XHTML/CSS 2.1 support for styled documents.
• Ideal for simpler documents without JavaScript requirements.
How to Generate PDF from HTML in Java Using Flying Saucer
iText• Professional PDF library with advanced features (forms, signatures, encryption).
• Perfect for complex business documents.
Creating Professional PDFs with iText in Java
OpenPDF• Open-source fork of iText under LGPL license.
• Direct PDF construction from Java code.
OpenPDF in Java: How to Generate PDFs
HTML to PDF API
like PDFBolt
• Cloud-based service with no local setup required.
• Perfect for startups, microservices and large-scale applications.
PDFBolt HTML to PDF API Documentation

Conclusion

Apache PDFBox stands out as a powerful Java library for programmatic PDF generation, offering developers complete control over document creation without relying on external browser engines. The combination of PDFBox with JSoup for HTML parsing creates a flexible solution that's particularly valuable for generating business-critical documents like invoices, reports, and certificates where precise layout control and consistent formatting are essential.

However, PDFBox requires more development effort compared to direct HTML to PDF converters since it doesn't natively render HTML/CSS layouts. You'll need to manually handle positioning, styling, and page flow, making it less suitable for converting complex existing web pages. For applications requiring full CSS3 support or quick conversion of styled HTML content, solutions like Playwright or Flying Saucer may be more appropriate.

Choose Apache PDFBox when building systems that require pixel-perfect positioning, custom typography, and enterprise-grade features including encryption and digital signatures. While the programmatic approach demands more initial setup than direct conversion tools, it ultimately delivers the flexibility and precision needed for mission-critical document generation in Java EE and Spring applications where document structure is predictable and controlled.

They say think outside the box, but sometimes it’s more like think with the box...
Or rather, with PDFBox! 📦