Generating PDFs in Java: four libraries compared

Five business documents and a batch of letters, made by each library the way its own documentation shows, every PDF checked, in IBM Plex Sans, and all of them again in Noto Sans. The other measurements, the features of each library, the method and the tables of every figure are further down this page.

I wrote PDFjet, one of the four libraries compared here, and have been updating it since 2001. For the last two months I have worked on it using Claude. I commissioned this benchmark; Claude chose the documents, their content and the way each library is used, wrote the programs, ran them and wrote the tables. Both are reasons to be suspicious of the results, so everything needed to check them is published in github.com/edragoev1/pdf-libs-benchmarks: the specification of the documents, every program, the scripts that run and check them, and the raw results.

If we have configured a library badly, or used it in a way its users would not, please tell us or open an issue, and we will fix the configuration and run the benchmark again.

Lower is better, and the best is in navy. Each chart starts at zero and is scaled to its own largest value.

Time per document. These tests use IBM Plex Sans.

Invoice, 1 page

warm, ms

PDFBox3.0
iText4.4
OpenPDF2.5
PDFjet1.6

Statement, 12 pages

warm, ms

PDFBox5.8
iText70.3
OpenPDF17.0
PDFjet5.5

Report, 18 pages

warm, ms

PDFBox151.9
iText24.1
OpenPDF5.8
PDFjet5.3

Multilingual notice

warm, ms

PDFBox2.0
iText2.5
OpenPDF1.4
PDFjet1.3

1,000 letters, SVG logo

warm, s

PDFBox1.30
iText1.89
OpenPDF1.00
PDFjet0.88

Time per document, Noto Sans. These tests use Noto Sans, which has many more glyphs than IBM Plex Sans.

Invoice, Noto Sans

warm, ms

PDFBox3.1
iText5.8
OpenPDF3.4
PDFjet2.6

Statement, Noto Sans

warm, ms

PDFBox5.4
iText73.6
OpenPDF18.2
PDFjet6.5

Report, Noto Sans

warm, ms

PDFBox149.8
iText28.6
OpenPDF6.8
PDFjet6.5

Multilingual, Noto Sans

warm, ms

PDFBox1.7
iText4.2
OpenPDF2.0
PDFjet2.1

1,000 letters, SVG logo

warm, s

PDFBox1.23
iText3.08
OpenPDF1.83
PDFjet1.84

The size of the PDFs and memory

Lower is better, and the best is in navy. Each chart starts at zero and is scaled to its own largest value.

The size of the PDFs. These tests use IBM Plex Sans.

Size, the invoice

KB

PDFBox19
iText24
OpenPDF22
PDFjet25

Size, the statement

KB

PDFBox42
iText62
OpenPDF59
PDFjet64

Size, the report

KB

PDFBox1,816
iText1,824
OpenPDF1,817
PDFjet1,823

Size, the notice

KB

PDFBox22
iText30
OpenPDF25
PDFjet29

Size, one letter

KB

PDFBox11
iText17
OpenPDF15
PDFjet18

Memory allocated per document. These tests use IBM Plex Sans.

Memory, the invoice

MB allocated

PDFBox1.5
iText3.6
OpenPDF2.2
PDFjet1.6

Memory, the statement

MB allocated

PDFBox5.6
iText71.6
OpenPDF56.0
PDFjet4.7

Memory, the report

MB allocated

PDFBox29.8
iText26.9
OpenPDF13.4
PDFjet12.0

Memory, the notice

MB allocated

PDFBox1.8
iText1.8
OpenPDF0.6
PDFjet1.0

Memory, one letter

MB allocated

PDFBox1.4
iText2.4
OpenPDF0.5
PDFjet1.0

The first PDF of a fresh process

Lower is better, and the best is in navy. Each chart starts at zero and is scaled to its own largest value.

The first PDF of a fresh process, as on a cold start. These tests use IBM Plex Sans.

The invoice

first PDF, ms

PDFBox189.9
iText249.8
OpenPDF190.4
PDFjet49.8

The statement

first PDF, ms

PDFBox203.9
iText540.2
OpenPDF251.9
PDFjet85.9

The report

first PDF, ms

PDFBox401.4
iText368.2
OpenPDF184.8
PDFjet82.4

The notice

first PDF, ms

PDFBox159.0
iText205.0
OpenPDF127.4
PDFjet38.3

The first letter

first PDF, ms

PDFBox321.6
iText352.7
OpenPDF432.8
PDFjet40.5

The peak memory of a fresh process, making its first PDF. These tests use IBM Plex Sans.

The invoice

peak MB, the process

PDFBox82
iText89
OpenPDF83
PDFjet54

The statement

peak MB, the process

PDFBox91
iText168
OpenPDF135
PDFjet65

The report

peak MB, the process

PDFBox121
iText140
OpenPDF101
PDFjet71

The notice

peak MB, the process

PDFBox80
iText85
OpenPDF71
PDFjet53

The first letter

peak MB, the process

PDFBox107
iText133
OpenPDF140
PDFjet54

The lines of code of the programs

Lower is better, and the best is in navy. Each chart starts at zero and is scaled to its own largest value.

Lines of code of each program. Without blank lines and comments.

The invoice

lines

PDFBox28
iText39
OpenPDF57
PDFjet31

The statement

lines

PDFBox26
iText30
OpenPDF37
PDFjet25

The report

lines

PDFBox53
iText22
OpenPDF32
PDFjet32

The notice

lines

PDFBox24
iText17
OpenPDF23
PDFjet17

The letters

lines

PDFBox34
iText17
OpenPDF32
PDFjet20

The helpers the programs share, and the total. The helpers are counted in the total.

The helpers

lines

PDFBox91
iText45
OpenPDF65
PDFjet37

All the programs

lines

PDFBox256
iText170
OpenPDF246
PDFjet162

PDFjet in Java, C#, Go and Swift

Lower is better, and the best is in navy. Each chart starts at zero and is scaled to its own largest value.

Time per document, the four ports. These tests use IBM Plex Sans.

Invoice, 1 page

warm, ms

Java1.6
C#1.1
Go1.6
Swift1.7

Statement, 12 pages

warm, ms

Java5.5
C#6.0
Go4.8
Swift11.0

Report, 18 pages

warm, ms

Java5.3
C#7.4
Go5.9
Swift13.5

Multilingual notice

warm, ms

Java1.3
C#0.8
Go1.1
Swift1.6

1,000 letters, SVG logo

warm, s

Java0.88
C#0.67
Go0.93
Swift1.58

Time per document, Noto Sans, the four ports. These tests use Noto Sans, which has many more glyphs than IBM Plex Sans.

Invoice, Noto Sans

warm, ms

Java2.6
C#1.5
Go2.4
Swift2.6

Statement, Noto Sans

warm, ms

Java6.5
C#6.5
Go5.6
Swift13.0

Report, Noto Sans

warm, ms

Java6.5
C#7.9
Go6.8
Swift14.9

Multilingual, Noto Sans

warm, ms

Java2.1
C#1.4
Go1.8
Swift2.5

1,000 letters, SVG logo

warm, s

Java1.84
C#1.28
Go1.66
Swift2.54

PDFjet in Java, C#, Go and Swift: the size of the PDFs and memory

Lower is better, and the best is in navy. Each chart starts at zero and is scaled to its own largest value.

The size of the PDFs, the four ports. These tests use IBM Plex Sans.

Size, the invoice

KB

Java25
C#25
Go26
Swift27

Size, the statement

KB

Java64
C#63
Go63
Swift71

Size, the report

KB

Java1,823
C#1,822
Go1,823
Swift1,825

Size, the notice

KB

Java29
C#29
Go29
Swift31

Size, one letter

KB

Java18
C#18
Go18
Swift19

Memory allocated per document, the four ports. These tests use IBM Plex Sans.

Memory, the invoice

MB allocated

Java1.6
C#1.4
Go2.6
Swift4.0

Memory, the statement

MB allocated

Java4.7
C#5.5
Go4.8
Swift7.3

Memory, the report

MB allocated

Java12.0
C#12.5
Go9.0
Swift18.1

Memory, the notice

MB allocated

Java1.0
C#1.0
Go1.3
Swift2.8

Memory, one letter

MB allocated

Java1.0
C#0.9
Go1.5
Swift2.7

PDFjet in Java, C#, Go and Swift: the first PDF

Lower is better, and the best is in navy. Each chart starts at zero and is scaled to its own largest value.

The first PDF of a fresh process, as on a cold start, the four ports. These tests use IBM Plex Sans.

The invoice

first PDF, ms

Java49.8
C#54.5
Go3.4
Swift3.5

The statement

first PDF, ms

Java85.9
C#64.4
Go7.5
Swift14.7

The report

first PDF, ms

Java82.4
C#69.6
Go8.5
Swift18.9

The notice

first PDF, ms

Java38.3
C#42.6
Go2.6
Swift5.5

The first letter

first PDF, ms

Java40.5
C#55.9
Go2.2
Swift3.3

The peak memory of a fresh process, making its first PDF. These tests use IBM Plex Sans.

The invoice

peak MB, the process

Java54
C#44
Go10
Swift28

The statement

peak MB, the process

Java65
C#48
Go12
Swift34

The report

peak MB, the process

Java71
C#53
Go14
Swift34

The notice

peak MB, the process

Java53
C#43
Go9
Swift27

The first letter

peak MB, the process

Java54
C#44
Go9
Swift27

The libraries

LibraryVersionLicenseJava
PDFjet (core)9.1.7MIT8 and later
Apache PDFBox3.0.8Apache 2.08 and later
iText Core9.8.0AGPL-3.0 or commercial8 and later
OpenPDF3.0.5LGPL-2.1 or MPL-2.021 and later

The newest releases on Maven Central on 5 November 2026. For the letters with an SVG logo, the modules the others need to draw SVG, each at its newest release: iText's svg module 9.8.0, Apache Batik 1.20, and for PDFBox the pdfbox-graphics2d bridge 3.0.5.

Each library is used the way its own documentation shows: iText's and OpenPDF's Document for flowing text, PDFBox's content streams with lines wrapped by hand, as it has no layout of its own, and PDFjet's TextFrame and Table, and for the report TextFlow. Every program reads the font files from disk once, into memory, and each document makes its fonts from those bytes, as a server would: iText and OpenPDF with their own default caches, PDFBox with its TrueType font parsed once and reused, as its API allows; PDFjet has no font cache and parses the bytes for each document. We ran iText both with its font cache and without it: without it, iText takes 1 to 5 ms more per document. The results here are with font caching.

Features

What each library has, as of the versions above, from its source at the release tag, its Maven POM and its documentation; only the features the documents use were tested here. A module is another jar of the same project; an add-on is a separate product of the same company.

PDFBox has no table of its own, so its programs here draw the tables by hand: a gray box behind the header, each cell's text measured to align the numbers right, a rule under each row, the header again on each new page, and the page numbers in a second pass once the pages are counted.

The other jars each of the others needs at run time, from its POM: PDFBox pdfbox-io, fontbox and commons-logging; iText io, commons, jackson-databind, slf4j-api and bouncy-castle-connector, and its layout jar for tables and flowing text; OpenPDF none: Maven adds brotli4j, its service jar and a native jar for the platform, needed only for Brotli compression (OpenPDF ran here without them). pdfHTML, pdfSweep and pdfOCR are iText add-ons under the same terms as iText Core, AGPL or commercial; pdfCalligraph, pdfRender and pdfXFA are commercial only. PDFjet Pro is PDFjet's add-on, commercial.

Far more people have worked on PDFBox, iText and OpenPDF than on PDFjet, and far more people use them, with more answers on the web for every question; iText also has a company behind it with paid support.

FeaturePDFBoxiTextOpenPDFPDFjet
Tablesnolayout module✓✓
Text wrapped into paragraphsno, by handlayout module✓✓
Text flowing onto new pagesnolayout module✓✓
Tagged PDF, PDF/UA-1by hand: your code builds the structure tree, numbers the marked content and builds the parent tree✓ tags; pdfua module for PDF/UAby hand: your code marks the content of each element; OpenPDF builds the parent tree✓
PDF/UA-2nopdfua modulenono
PDF/Aby hand; xmpbox module for the metadatapdfa module, 1a to 4f1a to 3u1a to 3b
SVGthrough Apache Batik and pdfbox-graphics2dsvg modulethrough Apache Batik✓
Barcodes, 1Dnobarcodes module✓✓
QR codesnobarcodes moduleno✓
Data Matrixnobarcodes module✓✓
PDF417nobarcodes module✓✓
Chartsnononobar, line, scatter, donut
Markdown to PDFnonono✓
HTML to PDFnopdfHTML add-onopenpdf-html moduleno
ImagesJPEG, PNG, GIF, BMP, TIFFthose, WMF, JPEG 2000, JBIG2; WebP with the webp-image-support modulethose, WMF, JPEG 2000JPEG, PNG, BMP
KerningnopdfCalligraph, commercialthe old kern table only, which neither font here has; GPOS through Java AWT or Apache FOP✓
Ligatures (GSUB)✓ by default, turned off herepdfCalligraph, commercialthrough Java AWT or Apache FOPno
ArabicnopdfCalligraph, commercial✓✓
Devanagari and other Indic scriptsDevanagari, Bengali, Gujarati, without mark positioningpdfCalligraph, commercialthrough Java AWT or Apache FOPno
Hyphenationnohyph module✓ patterns not includedsoft hyphens; five languages in Pro, commercial
Extract text✓✓✓no
Render a page to an image✓pdfRender, commercialopenpdf-renderer moduleno
Change existing PDFs✓✓✓add content, merge, split
Fill or create form fields✓forms module✓no
Digital signatures✓ with Bouncy Castlesign module, with Bouncy Castle✓ with Bouncy Castle, PKCS #7, not PAdESPro, commercial
Long-term signatures, PAdES B-LT and B-LTAby copying its example code (AddValidationInformation), not in the librarysign modulenoPro, commercial
Password encryption, AES-256✓✓✓✓
Certificate encryption✓ with Bouncy Castle✓ with Bouncy Castle✓ with Bouncy Castleno
PDF 2.0a 2.0 header, setVersion✓a 2.0 header by defaultno, PDF 1.7
Object streams, for smaller files✓ by default✓ an option✓ an optionno
RedactionnopdfSweep add-onnono
OCRnopdfOCR add-onnono
XFA formsreads the datafills the data, forms module; flattens, pdfXFA, commercialreads, fills the datano
Electronic invoices: ZUGFeRD, Factur-X, XRechnungby hand: PDF/A-3 with the XML attachedby hand, with the pdfa moduleby handPro, commercial
Check existing files for PDF/Apreflight module, PDF/A-1bnonono
No other jars needednono✓; Maven adds brotli4j, needed only for Brotli compression✓
Java8 and later8 and later21 and later8 and later
Also for—.NET—.NET, Go, Swift
LicenseApache 2.0AGPL-3.0 or commercialLGPL-2.1 or MPL-2.0MIT

What was measured

Five tasks, specified exactly in SPEC.md, the same text and the same layout in every library:

All in IBM Plex Sans, embedded as subsets, and all again in Noto Sans, a much larger font.

Method. Each library in its own fresh JVM, in a random order, three rounds; within a JVM the tasks run in the same fixed order for every library. Each task is warmed up for at least 20 runs and 3 seconds, then timed over 15 runs (the letters: 2 warm-up batches, 5 timed); the figure is the median of the three round medians. An asterisk (*) marks a figure whose rounds differ by more than 5%. The first PDF of a fresh process is the median of 5 processes. OpenJDK 25, .NET 10, Go 1.27, Swift 6.4, on an AMD Ryzen 5 5600G with Linux, after a reboot, in the performance power profile, nothing else running. After the run every PDF is checked, and the log is published: MuPDF opens it with no warning, it has the expected pages, the check strings of SPEC.md are in its text (the last sentence of every language for the notice), both fonts are embedded as subsets, and qpdf --check passes.

Results

Every measurement is drawn as a chart above, and the tables of every figure, with PDFjet in C#, Go and Swift beside the four libraries, are in the appendix at the end.

To draw the SVG logo the others need another module or library, and each was given the one its users would choose:

Libraryto draw SVG it needs
PDFBoxApache Batik and pdfbox-graphics2d
iTextiText's svg module
OpenPDFApache Batik
PDFjetnothing else

PDFjet in C#, Go and Swift

PDFjet is also written in C#, Go and Swift: the same classes and the same public API in each, and the same documents laid out the same way, which an automated check compares on every change. The last three columns of the tables in the appendix are the same documents made by each of them. For a program that makes PDFs, what counts is what the port of its language makes, and how fast: that is what those columns show.

Each port compresses its pages with the Deflate its language offers, but for Swift: Swift offers none on Linux, so the Swift port has an encoder of its own, which searches less than zlib. Its PDFs, the largest of the four, are larger because of that encoder, not because of Swift.

Reproduce it

Everything is in github.com/edragoev1/pdf-libs-benchmarks: SPEC.md, the text of the notice, the logo and the photos, the program of each library and port, the run and check scripts, and the raw results with their logs. java/run.sh runs the four Java libraries (about 15 minutes) and java/check.sh checks every PDF; run-all.sh runs everything, as for these results; dotnet/, go/ and swift/ run PDFjet's other ports the same way; make-tables.py makes every table of this report from the results.

Judgment calls

Appendix: the tables

Time per document, IBM Plex Sans

Time per document, warm, ms (the letters: the batch of 1,000, s). In every table lower is better, faster, less memory, smaller files, fewer lines, and the best of the four Java libraries is in bold. The last three columns are PDFjet in C#, Go and Swift, shown with them but not ranked against them.

DocumentPDFBoxiTextOpenPDFPDFjet JavaPDFjet C#PDFjet GoPDFjet Swift
invoice, 1 page3.0 *4.42.51.61.1 *1.61.7 *
statement, 12 pages5.8 *70.3 *17.05.56.0 *4.811.0
report, 18 pages151.924.15.85.37.45.9 *13.5
multilingual, 1 page2.02.51.41.30.8 *1.1 *1.6
the 1,000 letters, PNG logo, s2.491.971.911.351.031.371.50
the 1,000 letters, SVG logo, s1.301.891.000.880.670.931.58

* The three rounds of this figure differ by more than 5%; the figure is their median.

Time per document, Noto Sans

With Noto Sans, a large font, ms (the letters: the batch of 1,000, s):

DocumentPDFBoxiTextOpenPDFPDFjet JavaPDFjet C#PDFjet GoPDFjet Swift
invoice3.1 *5.83.42.61.52.4 *2.6 *
statement5.4 *73.6 *18.26.56.55.613.0
report149.828.66.86.57.9 *6.8 *14.9
multilingual1.74.22.02.11.4 *1.8 *2.5
the 1,000 letters, SVG logo, s1.233.081.831.841.281.662.54

* The three rounds of this figure differ by more than 5%; the figure is their median.

The size of the PDFs

The size of the PDFs, KB:

DocumentPDFBoxiTextOpenPDFPDFjet JavaPDFjet C#PDFjet GoPDFjet Swift
invoice19242225252627
statement42625964636371
report1,8161,8241,8171,8231,8221,8231,825
multilingual22302529292931
one letter11171518181819

Memory allocated per document

Memory allocated per document, MB. Each runtime counts it its own way: Java and C# the bytes the timing thread allocates, Go every allocation of the process (Go's TotalAlloc), Swift every malloc of the thread:

DocumentPDFBoxiTextOpenPDFPDFjet JavaPDFjet C#PDFjet GoPDFjet Swift
invoice1.53.62.21.61.42.64.0
statement5.671.656.04.75.54.87.3
report29.826.913.412.012.59.018.1
multilingual1.81.80.61.01.01.32.8
one letter1.42.40.51.00.91.52.7

The first PDF of a process

The first PDF of a fresh process, each of the five documents, as a serverless function or a newly started container pays it on a cold start, or a desktop app on its first export, ms; then the peak memory of the whole process, mostly the JVM itself, MB:

PDFBoxiTextOpenPDFPDFjet JavaPDFjet C#PDFjet GoPDFjet Swift
the invoice, ms189.9249.8190.449.854.53.43.5
the statement, ms203.9540.2251.985.964.47.514.7
the report, ms401.4368.2184.882.469.68.518.9
the notice, ms159.0205.0127.438.342.62.65.5
the first letter, ms321.6352.7432.840.555.92.23.3
the invoice, the whole process, ms2403002308080under 1010
the invoice, peak memory, MB82898354441028
the statement, peak memory, MB9116813565481234
the report, peak memory, MB12114010171531434
the notice, peak memory, MB8085715343927
the first letter, peak memory, MB1071331405444927

Lines of code

Lines of code of each program, without blank lines and comments; "helpers" are the methods a library's tasks share (fonts, cells, footers, word wrapping), counted in the total:

invoicestatementreportlettersmultilingualhelperstotal
PDFBox282653342491256
iText393022171745170
OpenPDF573732322365246
PDFjet Java312532201737162
PDFjet C#322532201735161
PDFjet Go362635262035178
PDFjet Swift292231181545160