Document API vs wkhtmltopdf vs Chrome Headless: Which PDF Engine to Choose?
Choosing a PDF generation engine is one of those decisions that seems straightforward until you are three months into production, debugging rendering inconsistencies at 2 AM. The landscape has changed significantly in recent years: wkhtmltopdf is deprecated, Chrome Headless has matured, and purpose-built document APIs have emerged as a serious alternative.
This article provides an honest, technical comparison of the three main approaches to server-side PDF generation. We will examine rendering quality, performance, memory consumption, container-friendliness, and long-term maintainability.
The Contenders
wkhtmltopdf
A command-line tool that uses a patched version of QtWebKit to render HTML to PDF. For years it was the default choice for open-source PDF generation. However, the project was archived and is no longer maintained. The underlying WebKit fork is frozen, meaning no new CSS features, no security patches, and growing incompatibility with modern HTML.
Chrome Headless (Puppeteer / Playwright)
Google Chrome running without a GUI, controlled via the DevTools Protocol. Libraries like Puppeteer (Node.js) and Playwright (multi-language) provide high-level APIs. This approach renders HTML exactly as Chrome would, supporting the latest CSS and JavaScript features.
Document API (JVM-native or Cloud API)
Purpose-built libraries like OpenHtmlToPdf (Java) or cloud APIs like Doxnex that handle the entire pipeline: template management, rendering, storage, and delivery. These solutions trade some CSS flexibility for dramatically better performance and operational simplicity.
Comparison Matrix
| Criteria | wkhtmltopdf | Chrome Headless | Document API |
|---|---|---|---|
| Rendering quality | Outdated WebKit; CSS3 gaps | Excellent; full modern CSS | Good; CSS 2.1 + paged media |
| Speed (single doc) | 500ms - 2s | 1s - 3s | 100ms - 300ms |
| Memory per render | 50 - 150 MB | 200 - 500 MB | 20 - 50 MB |
| Concurrent renders | Process per render | Tab per render | Thread per render |
| Docker image size | 300 - 600 MB | 500 MB - 1.2 GB | 100 - 200 MB |
| JavaScript support | Partial (old engine) | Full | None |
| Maintenance status | Archived / EOL | Active (Google) | Active |
| Security | Unpatched vulnerabilities | Regular updates | No browser attack surface |
Performance Deep-Dive
We benchmarked all three approaches generating the same two-page invoice template with a company logo, 20 line items in a table, and two embedded fonts. Tests were run on a 4-core, 8 GB RAM machine inside Docker containers.
Throughput
The results were dramatic. The JVM-native document API generated 180 documents per second with 8 threads. Chrome Headless managed 12 documents per second with 4 concurrent browser contexts before hitting memory limits. wkhtmltopdf achieved 8 documents per second as each render spawned a new process.
Memory Under Load
Under sustained load of 50 concurrent requests, Chrome Headless peaked at 3.8 GB of RAM. The document API peaked at 380 MB. wkhtmltopdf peaked at 1.2 GB but crashed intermittently due to memory fragmentation in the QtWebKit engine.
Container-Friendliness
Modern deployments run in containers on Kubernetes or similar orchestrators. The PDF engine you choose has a massive impact on your infrastructure costs and operational complexity.
wkhtmltopdf in Docker
Requires installing X11 libraries, fonts, and the binary itself. The resulting image is bloated and fragile. Different Linux distributions produce different rendering results due to font rendering differences. Do not use for new projects.
Chrome Headless in Docker
Requires a large base image with Chrome and its dependencies. You must configure --no-sandbox (security trade-off), increase shared memory with --shm-size=2g, and install font packages. Zombie process cleanup is a common operational issue.
# Chrome Headless Dockerfile - note the complexity
FROM node:20-slim
RUN apt-get update && apt-get install -y \
chromium fonts-liberation fonts-noto-cjk \
libgbm1 libnss3 libatk-bridge2.0-0 \
--no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
# Image size: ~900MB
Document API in Docker
A JVM-native solution needs only a JRE base image and font files. No system dependencies, no sandboxing concerns, no process management.
# Document API Dockerfile - simple and small
FROM eclipse-temurin:21-jre-alpine
COPY app.jar /app/app.jar
COPY fonts/ /app/fonts/
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
# Image size: ~180MB
When to Choose Each Option
Choose Chrome Headless When:
- You need pixel-perfect rendering of complex CSS3 layouts (Flexbox, Grid, animations frozen to PDF)
- Your templates rely on JavaScript for chart rendering (Chart.js, D3)
- Throughput requirements are low (under 10 documents per second)
- You have the infrastructure budget for high-memory containers
Choose a Document API When:
- You need high throughput (50+ documents per second)
- Container resource efficiency matters
- Your documents follow structured templates (invoices, reports, labels)
- You want managed infrastructure with built-in template management and storage
- Security is a priority and you want to avoid browser attack surfaces
Do Not Choose wkhtmltopdf
There is no scenario where wkhtmltopdf is the right choice for a new project in 2026. If you have an existing integration, budget time for migration. The unpatched security vulnerabilities alone make it a liability.
The Doxnex Approach
Doxnex uses a JVM-native rendering engine (Thymeleaf + OpenHtmlToPdf) behind a REST API. You send a template and data, and receive a PDF. No browser processes, no heavy containers, no infrastructure headaches. It is the performance of a native library with the convenience of a cloud API.
Frequently Asked Questions
Is wkhtmltopdf still maintained?
No. wkhtmltopdf was archived and is no longer actively maintained. It relies on a patched version of QtWebKit that is years behind modern web standards. Known security vulnerabilities will not be patched. If you are starting a new project, do not choose wkhtmltopdf. If you have an existing integration, plan a migration to a maintained alternative.
How much memory does Chrome Headless use for PDF generation?
A single Chrome Headless instance typically consumes 200-500 MB of RAM, depending on page complexity. Each concurrent render requires its own browser context. For high-throughput scenarios, this means you may need several gigabytes of RAM just for PDF generation, making it impractical without significant infrastructure investment.
Can I use Chrome Headless in a Docker container?
Yes, but it requires careful configuration. Chrome needs the --no-sandbox flag in containers (which reduces security), shared memory must be increased (--shm-size=2g), and you need to install numerous system dependencies for font rendering. The resulting Docker image is typically 500MB-1GB. JVM-native solutions produce images under 200MB.
What is the fastest PDF generation approach for server-side rendering?
JVM-native libraries like OpenHtmlToPdf combined with a template engine offer the fastest server-side PDF generation, typically under 200ms per document. API-based services like Doxnex abstract this complexity and add caching, template management, and scalability on top, while maintaining sub-second response times including network overhead.