Barcodes with iText, ZXing & Okapi
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
A barcode on a business document has two jobs that pull in different directions. It must be machine readable, which
means exact module sizes, a quiet zone around it and enough contrast, and it must sit inside a layout that was designed
for people. In a PDF the best way to satisfy both is to draw the symbol as vector graphics: every dark module is a
small filled rectangle in the page content, so the code stays razor sharp at any zoom level and at any printer
resolution, and the file stays small. This page shows three Java libraries that can do that and how to combine them
with iText 9.8: the barcodes module of iText itself, the ZXing ("Zebra Crossing") encoder, and Okapi Barcode, which
covers many symbologies the other two do not. The running example is the Bookshelf Billing invoice, which carries an
invoice number as a Code 128 barcode, an EAN-13 per book line and a QR code with the address of the online copy. What a
symbology is, what error-correction levels, quiet zones and module sizes mean, and when to use vector or raster output
is explained in Barcodes & QR Codes: Fundamentals, and the
regulated codes that go on invoices (VeriFactu, TicketBAI, EPC and Swiss QR) in
Regulated QR Codes on Invoices & Payments. Barcodes in
JasperReports templates are in Barcodes in JasperReports.
Versions are stated as of 2026-10-09: iText Core 9.8.0 and pdfHTML 6.3.4 on Java 21, ZXing 3.5.4 and Okapi Barcode 0.5.6. Every example on this page was compiled and run against exactly these versions. The resulting PDFs were inspected for the absence of image objects (all symbols are vector content), and every symbol was then rendered to a bitmap at 300 dpi with Apache PDFBox 3.0.8 and decoded again with ZXing’s reader, which is the test shown at the end of the page. The Code 128, EAN-13, Code 39, QR, PDF417 and Data Matrix symbols decoded to the text that had been encoded; Okapi’s GS1 Code 128 and GS1 Data Matrix decoded to the application identifiers and values that had been given. No scanner hardware or Acrobat was used, so how a particular handheld reader behaves with the printed output was not tested.
Which library for which job
| Library | What it gives you | Output you work with | Licence |
|---|---|---|---|
iText |
Code 128 (including GS1-128), EAN-13, EAN-8, UPC-A, UPC-E and supplements, Code 39, Codabar, Interleaved 2 of 5, MSI, POSTNET, PDF417, Data Matrix and QR |
A |
AGPLv3 or commercial, the same as iText Core |
ZXing |
Encoders for QR, Data Matrix, Aztec, PDF417, Code 128, Code 39, Code 93, EAN, UPC, ITF and Codabar, and the readers for the same formats |
A |
Apache-2.0 |
Okapi Barcode 0.5.6 |
More than 50 symbology classes, including GS1 DataBar, composite symbols, MaxiCode, Code 16K, many postal codes (Royal Mail, Japan Post, USPS Intelligent Mail, KIX, Australia Post), Micro QR, Swiss QR code and GS1 data entry with application identifiers in square brackets |
A list of |
Apache-2.0 |
The licence of the iText column matters: the barcodes module is part of iText Core, so a service that embeds it
without a commercial licence must itself be offered under the AGPLv3 (see
Introduction & Approaches for the consequences). ZXing and
Okapi are Apache-2.0 and can be used in either case. That is a real reason to prefer ZXing or Okapi for the encoder in a
closed-source service that draws on a different PDF library, for instance PDFBox. The licences were read from the pom.xml files of the Okapi 0.5.6 and ZXing 3.5.4 artifacts.
The practical rule that follows from the table is short. Use the iText module when you are already in an iText document and need one of its symbologies: it needs no extra dependency and its output is a ready-to-place object. Use ZXing when you need control over the matrix (colours, rounded modules, a logo in the centre of a QR code, SVG for the web, or a licence that is not AGPL) or a format iText lacks, such as Aztec or Code 93. Use Okapi for GS1 symbologies, postal codes and anything exotic. Barcode4J, which JasperReports uses, is the subject of the JasperReports page of this section, and the older Barbecue library is abandoned.
What changed since iText 5 and the old Barcode classes
Books written for iText 1 to 5 (among them iText in Action, which was consulted for the concept order of this page
and is not reproduced) create a barcode with com.itextpdf.text.pdf.Barcode128 or BarcodeEAN, give it a
PdfContentByte, and call createImageWithBarcode to obtain an Image that is then added to a Document or a
PdfPCell. That works with the com.lowagie.text packages of iText 1 and 2 and with the com.itextpdf.text packages of
iText 5; the packages were removed in iText 7, which is a rewrite, and the licence changed from MPL and LGPL to AGPLv3 or
commercial (finding O1 of this section’s source analysis). In iText 7, 8 and 9 the classes live in the
com.itextpdf.barcodes package of the barcodes module, the constructors take the PdfDocument (and optionally a
PdfFont for the human-readable text), and the preferred result is createFormXObject, a vector object that you wrap in
the layout Image. The old raster route, createAwtImage, still exists, but it needs java.awt and produces a bitmap
you rarely want, so it is not used here. The AWT-based graphics export of iText 5 (PdfGraphics2D, finding O7) is not
in iText Core either: drawing is done with PdfCanvas. All examples use Java 21 (finding O28): try-with-resources for
the documents, Map.of, text blocks and small static helper methods instead of the anonymous classes of the old
books.
Maven coordinates
<properties>
<maven.compiler.release>21</maven.compiler.release>
<itext.version>9.8.0</itext.version>
</properties>
<dependencies>
<!-- iText Core: kernel, layout, barcodes, ... (AGPLv3 or commercial) -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-core</artifactId>
<version>${itext.version}</version>
<type>pom</type>
</dependency>
<!-- ZXing encoder and readers (Apache-2.0) -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.4</version>
</dependency>
<!-- Okapi Barcode (Apache-2.0) -->
<dependency>
<groupId>uk.org.okapibarcode</groupId>
<artifactId>okapibarcode</artifactId>
<version>0.5.6</version>
</dependency>
<!-- only for the round-trip test at the end of the page -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.8</version>
<scope>test</scope>
</dependency>
</dependencies>
The itext-core pom brings in the kernel, layout, barcodes and the other modules (see
iText Architecture & Modules). If you want only the
barcodes, depend on com.itextpdf:barcodes instead; it pulls in the kernel. For the HTML example you also need
com.itextpdf:html2pdf:6.3.4 (see HTML to PDF with pdfHTML), and a
logging binding for SLF4J. The examples were run with the slf4j-nop binding to keep the output clean. Okapi Barcode
uses java.awt.Font for the text metrics, so the JDK module java.desktop must be present; it works in headless mode,
which is the normal setting of a server (see
Getting Started with Spring Boot).
Barcodes with the iText barcodes module
The invoice header: Code 128, EAN-13 and a QR code
All iText barcode classes follow the same pattern. You create the object (the 1D symbologies take the PdfDocument and
a PdfFont, the 2D ones need neither), set the code and the geometry, and call createFormXObject, which returns a
PdfFormXObject containing only vector path operators. new Image(xObject) wraps it so that the layout engine can
place it like any other image: in a paragraph flow, in a table cell, at a fixed position or scaled.
The code below produces a one-page invoice header with a Code 128 barcode for the invoice number, a table row with an
EAN-13 barcode for a book (the ISBN-13 9780306406157 is the textbook example used in many specifications; it is a
valid number but it is used here only as sample data), and a QR code with the address of the online invoice. The
address is the invented example.com.
package demo;
import com.itextpdf.barcodes.Barcode128;
import com.itextpdf.barcodes.BarcodeEAN;
import com.itextpdf.barcodes.BarcodeQRCode;
import com.itextpdf.barcodes.qrcode.EncodeHintType;
import com.itextpdf.barcodes.qrcode.ErrorCorrectionLevel;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.UnitValue;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class ItextBarcodes {
/** A QR code as a vector Form XObject wrapped in a layout Image. */
static Image qr(PdfDocument pdf, String content, float moduleSize) {
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
BarcodeQRCode qr = new BarcodeQRCode(content, hints);
PdfFormXObject xObject = qr.createFormXObject(ColorConstants.BLACK, moduleSize, pdf);
Image image = new Image(xObject);
// Tagged PDF / PDF/UA: describe the barcode as a figure (see the PDF/A and PDF/UA page).
image.getAccessibilityProperties().setAlternateDescription("QR code with the address of the online invoice");
return image;
}
static Image code128(PdfDocument pdf, PdfFont font, String text) {
Barcode128 code = new Barcode128(pdf, font);
code.setCodeType(Barcode128.CODE128);
code.setCode(text);
code.setBarHeight(28); // user space units (points)
code.setX(0.8f); // width of the narrowest bar
code.setSize(8); // human-readable text size
code.setBaseline(9);
return new Image(code.createFormXObject(ColorConstants.BLACK, ColorConstants.BLACK, pdf));
}
/** Takes the first 12 digits and appends the EAN check digit computed by iText. */
static Image ean13(PdfDocument pdf, PdfFont font, String twelveDigits) {
BarcodeEAN code = new BarcodeEAN(pdf, font);
code.setCodeType(BarcodeEAN.EAN13);
code.setCode(twelveDigits + BarcodeEAN.calculateEANParity(twelveDigits));
code.setBarHeight(26);
code.setX(0.8f);
code.setSize(8);
code.setBaseline(8);
return new Image(code.createFormXObject(ColorConstants.BLACK, ColorConstants.BLACK, pdf));
}
public static void main(String[] args) throws IOException {
try (PdfDocument pdf = new PdfDocument(new PdfWriter("itext-barcodes.pdf"));
Document doc = new Document(pdf)) {
PdfFont font = PdfFontFactory.createFont();
doc.add(new Paragraph("Bookshelf Billing invoice BKS-2026-000123"));
doc.add(code128(pdf, font, "BKS-2026-000123"));
Table lines = new Table(UnitValue.createPercentArray(new float[] {3, 2}))
.useAllAvailableWidth();
lines.addCell(new Cell().add(new Paragraph("Sample book A")).setBorder(Border.NO_BORDER));
lines.addCell(new Cell().add(ean13(pdf, font, "978030640615")).setBorder(Border.NO_BORDER));
doc.add(lines);
doc.add(qr(pdf, "https://example.com/invoices/BKS-2026-000123", 3));
}
}
}
Source: iText Knowledge Base, How to generate 2D barcode as vector image, and the Java API of BarcodeQRCode, Barcode128 and BarcodeEAN.
Some details of that code are worth stating, because each one is a typical source of a barcode that does not scan.
-
The QR code takes hints.
BarcodeQRCodehas a constructor with aMap<EncodeHintType, Object>. The keys areERROR_CORRECTION(withErrorCorrectionLevel.L,M,QorH, which recover about 7, 15, 25 and 30 per cent of damaged codewords),CHARACTER_SETandMIN_VERSION_NR. The classEncodeHintTypeof iText is not the one of ZXing and not a Javaenum, so anEnumMapdoes not compile (the first attempt in the scratch project failed exactly there); use aHashMaporMap.of. Without hints iText chooses its own default (the hints map of a code created without them isnull), so pass the level explicitly. PreferMorQfor a printed invoice that will be folded, stapled or scanned from a screen, and keep in mind that a higher level makes a denser code. -
The module size is a parameter.
createFormXObject(Color, float moduleSize, PdfDocument)scales the code so that one module ismoduleSizepoints; the box of the object then grows with the version of the QR code. Without themoduleSize, a module is one point. As a guide, a module of 3 points is about 1.06 mm, enough for a good scanner at print resolution, and the size must never go below what the fundamentals page recommends for the reading distance. -
The quiet zone is included for the QR code. The form object for a version 1 code (21 by 21 modules) that was measured in the scratch project had a box of 29 by 29 units, which is 21 modules plus four on each side, the width the QR standard requires. For a 1D symbology the box of the form object (80.8 by 29.7 units for Code 128 of six characters with its text) starts at the first bar, so the quiet zone is not part of it, and an
Imagethat touches text or a border needs a margin (image.setMarginLeft(...)), a point that is easy to forget in a table cell. -
1D codes are described in user space units.
setXis the width of the narrowest bar,setNthe ratio of the wide to the narrow bar,setBarHeightthe height of the bars, andsetSizeandsetBaselinethe text under them. EAN codes with a human-readable line use thePdfFontgiven to the constructor. All of them are in points, so a bar width of 0.8 point is 0.28 mm. -
EAN-13 wants 13 digits in iText 9.8.0. The code in the invoice passes the first twelve digits and appends the check digit, which
BarcodeEAN.calculateEANParitycomputes; giving twelve digits tosetCodemakescreateFormXObjectfail with anArrayIndexOutOfBoundsException(reproduced with the version of this page). The check digit of the example was verified by decoding the rendered symbol, which returned9780306406157. -
Barcodes are figures for accessibility. A
PdfFormXObjectwrapped in anImageis a figure in the tag tree of a tagged document. The callimage.getAccessibilityProperties().setAlternateDescription(...)provides the text a screen reader speaks. A barcode that only contains an identifier is often marked as an artifact instead; the rules, and how to produce a conformant PDF/UA document, are in PDF/A & PDF/UA with iText.
Code 39, PDF417 and Data Matrix
The remaining symbologies of the module follow the same pattern. Code 39 has options for the characters printed under
the bars; PDF417 has an error level and a column count; Data Matrix has a size in modules and returns a status code from
setCode.
package demo;
import com.itextpdf.barcodes.Barcode39;
import com.itextpdf.barcodes.BarcodeDataMatrix;
import com.itextpdf.barcodes.BarcodePDF417;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import java.io.IOException;
public class Symbologies {
public static void main(String[] args) throws IOException {
try (PdfDocument pdf = new PdfDocument(new PdfWriter("symbologies.pdf"));
Document doc = new Document(pdf)) {
PdfFont font = PdfFontFactory.createFont();
Barcode39 c39 = new Barcode39(pdf, font);
c39.setCode("BKS-000123");
c39.setStartStopText(false); // do not print the * start and stop characters
c39.setBarHeight(24);
doc.add(new Paragraph("Code 39"));
doc.add(new Image(c39.createFormXObject(ColorConstants.BLACK, ColorConstants.BLACK, pdf)));
BarcodePDF417 pdf417 = new BarcodePDF417();
pdf417.setCode("Bookshelf Billing - delivery note DN-2026-000045");
pdf417.setErrorLevel(4);
pdf417.setCodeColumns(10);
doc.add(new Paragraph("PDF417"));
// Create the XObject at module size 1 and scale the Image: in 9.8.0 the bounding box of the
// PDF417 and Data Matrix XObjects does not follow the module-size overloads (checked by running it).
Image pdf417Image = new Image(pdf417.createFormXObject(ColorConstants.BLACK, pdf));
pdf417Image.scale(2.4f, 3f).setMarginTop(6);
doc.add(pdf417Image);
BarcodeDataMatrix dm = new BarcodeDataMatrix();
dm.setWidth(18); // force a square 18 x 18 symbol instead of the smallest that fits
dm.setHeight(18);
int status = dm.setCode("BKS-2026-000123"); // DM_NO_ERROR (0) when it fits
System.out.println("data matrix status " + status);
doc.add(new Paragraph("Data Matrix"));
Image dmImage = new Image(dm.createFormXObject(ColorConstants.BLACK, pdf));
dmImage.scaleAbsolute(72, 72);
dmImage.setMargins(8, 8, 8, 8); // 4 points per module plus an 8 point quiet zone
doc.add(dmImage);
}
}
}
Source: iText Java API, BarcodePDF417, BarcodeDataMatrix and Barcode39.
Two behaviours of iText 9.8.0 that were found by running the code are worth knowing. First, for BarcodePDF417 and
BarcodeDataMatrix the bounding box of the PdfFormXObject returned by the overloads that take a module size did not
follow the module size (for a Data Matrix of 32 by 8 modules the box stayed 32 by 8 units with a module size of 3, so
a layout Image showed only the corner of the code). The example therefore creates the object at the natural size and
scales the Image (scale for PDF417, scaleAbsolute for Data Matrix), which also scales the margins to the right
size. BarcodeQRCode is not affected: its box grew from 29 to 87 units with a module size of 3. If a future version
fixes the Data Matrix box, the scaled Image still works. Second, BarcodeDataMatrix with the default size chooses
the smallest symbol that fits, which for a short text is a rectangular one (32 by 8 modules for the example text). Many
readers prefer square symbols, so the example asks for 18 by 18 with setWidth and setHeight before calling
setCode. The status returned by setCode is 0 when the text fits (DM_NO_ERROR in the API documentation).
The module also has BarcodeCodabar, BarcodeInter25, BarcodeMSI, BarcodePostnet and BarcodeEANSUPP (the
two- and five-digit supplements of books and magazines). They are less used in invoices than in logistics and libraries.
GS1-128 is Barcode128 with setCodeType(Barcode128.CODE128_UCC) and the application identifiers in parentheses in
the text, for example (01)09506000134352(17)271231; a GS1 DataMatrix, a DataBar or a composite symbol are not in the
module, which is one reason to use Okapi.
Placing a barcode on every page: a page event
A code that is the same on every page, such as the QR code of the online invoice, is created once and stamped by a page
event handler, the technique of
Page Events: Headers, Footers & Watermarks. The handler holds the
PdfFormXObject, so the vector operators are stored once in the file and referenced from each page.
package demo;
import com.itextpdf.barcodes.BarcodeQRCode;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.event.AbstractPdfDocumentEvent;
import com.itextpdf.kernel.pdf.event.AbstractPdfDocumentEventHandler;
import com.itextpdf.kernel.pdf.event.PdfDocumentEvent;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import java.io.IOException;
public class QrEveryPage {
/** Stamps the same QR code, created once, in the bottom-right corner of every page. */
static class QrFooter extends AbstractPdfDocumentEventHandler {
private final PdfFormXObject qr;
private final float size;
QrFooter(PdfDocument pdf, String url, float size) {
BarcodeQRCode code = new BarcodeQRCode(url);
this.qr = code.createFormXObject(ColorConstants.BLACK, pdf); // module size 1: scaled below
this.size = size;
}
@Override
protected void onAcceptedEvent(AbstractPdfDocumentEvent event) {
PdfDocumentEvent e = (PdfDocumentEvent) event;
PdfPage page = e.getPage();
Rectangle box = page.getPageSize();
float scale = size / qr.getWidth();
new PdfCanvas(page.newContentStreamAfter(), page.getResources(), e.getDocument())
.saveState()
.concatMatrix(scale, 0, 0, scale, box.getRight() - 36 - size, box.getBottom() + 36)
.addXObjectAt(qr, 0, 0)
.restoreState();
}
}
public static void main(String[] args) throws IOException {
try (PdfDocument pdf = new PdfDocument(new PdfWriter("qr-every-page.pdf"));
Document doc = new Document(pdf)) {
pdf.addEventHandler(PdfDocumentEvent.END_PAGE,
new QrFooter(pdf, "https://example.com/invoices/BKS-2026-000123", 72));
for (int i = 0; i < 60; i++) {
doc.add(new Paragraph("Invoice line " + (i + 1)));
}
}
}
}
Source: iText Knowledge Base,
Page events for headers and footers,
adapted to AbstractPdfDocumentEventHandler of iText 9 and to a barcode. The code was run with a document of 60
paragraphs; the first page was rendered and its QR code decoded.
The handler scales the object with concatMatrix instead of asking for a module size, because the same object is used
at one fixed size. The PdfCanvas is created with page.newContentStreamAfter(), so the code is drawn above the
content of the page; use newContentStreamBefore() to put it behind. A code that differs on each page, such as a page
number or a per-page document id, needs its own handler instance state or a PdfFormXObject per page; for a code that
identifies the whole invoice, one object is both smaller and simpler.
Sizing and placement in tables and in flow
Four ways of positioning an iText barcode cover nearly every invoice:
-
In flow:
document.add(image). The image is a block element sized by its box (modules times module size, plus the quiet zone for QR). UsesetMarginBottomand friends for spacing. -
In a table cell:
cell.add(image), as the EAN-13 in the example. Set the cell border toBorder.NO_BORDERfor a clean look, and give the column a width that fits the box of the barcode plus the cell padding and the quiet zone, using the column widths of Tables. Check the output when a column is narrower than the symbol, because any scaling or overflow changes the module size, and a symbol below the minimum module size does not scan. -
At a fixed position:
image.setFixedPosition(pageNumber, x, y), for a pre-printed form or a label. Coordinates are in points from the lower-left corner, as explained in Low-level Canvas & Graphics. -
Straight on the canvas: every symbology has
placeBarcode(PdfCanvas, ...), which draws the bars at the current position and returns theRectangleit covered. It is the lowest-overhead route inside a custom renderer or a handler (see Renderers & Custom Layout) when you do not need layout.
Whichever route you choose, measure rather than guess: getBarcodeSize() returns the Rectangle the symbol will need,
so a layout can reserve space before drawing, and printed output should be tested with the real printer (see the testing
section below for the automated half).
ZXing: a BitMatrix drawn as vector rectangles or as SVG
ZXing’s job is to turn a string into a BitMatrix: a grid in which get(x, y) is true for a dark module. It does not
know about PDF. That is an advantage, because the matrix can be painted anywhere, and a small amount of code turns it
into exactly the vector graphics that iText produces by itself. The example below encodes a QR code, a Code 128 and a
Data Matrix, draws them as filled rectangles on a PdfCanvas, and writes the same QR matrix as an SVG file.
package demo;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.datamatrix.encoder.SymbolShapeHint;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import java.io.IOException;
import java.util.Map;
import java.util.Locale;
public class Zxing {
/** Encodes a QR code with exactly one BitMatrix cell per module; the quiet zone is part of the matrix. */
static BitMatrix qrMatrix(String content, ErrorCorrectionLevel level) throws WriterException {
Map<EncodeHintType, Object> hints = Map.of(
EncodeHintType.ERROR_CORRECTION, level,
EncodeHintType.CHARACTER_SET, "UTF-8",
EncodeHintType.MARGIN, 4);
return new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, 1, 1, hints);
}
/** Draws dark modules as vector rectangles, merging horizontal runs. Origin is the lower-left corner. */
static void draw(PdfCanvas canvas, BitMatrix m, float x, float y, float moduleW, float moduleH) {
canvas.saveState().setFillColor(ColorConstants.BLACK);
for (int row = 0; row < m.getHeight(); row++) {
int col = 0;
while (col < m.getWidth()) {
if (!m.get(col, row)) { col++; continue; }
int start = col;
while (col < m.getWidth() && m.get(col, row)) col++;
canvas.rectangle(x + start * moduleW,
y + (m.getHeight() - 1 - row) * moduleH,
(col - start) * moduleW, moduleH);
}
}
canvas.fill().restoreState();
}
/** The same matrix as an SVG document: one path, one "M x y h w v 1 h -w z" per run. */
static String toSvg(BitMatrix m) {
StringBuilder path = new StringBuilder();
for (int row = 0; row < m.getHeight(); row++) {
int col = 0;
while (col < m.getWidth()) {
if (!m.get(col, row)) { col++; continue; }
int start = col;
while (col < m.getWidth() && m.get(col, row)) col++;
path.append(String.format(Locale.ROOT, "M%d %dh%dv1h-%dz", start, row, col - start, col - start));
}
}
return "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 " + m.getWidth() + " " + m.getHeight()
+ "\" shape-rendering=\"crispEdges\"><rect width=\"100%\" height=\"100%\" fill=\"#fff\"/>"
+ "<path d=\"" + path + "\" fill=\"#000\"/></svg>";
}
public static void main(String[] args) throws IOException, WriterException {
BitMatrix qr = qrMatrix("https://example.com/invoices/BKS-2026-000123", ErrorCorrectionLevel.M);
// One row of Code 128 (height 1 asks for one row); bars are stretched to 40 points when drawn.
BitMatrix c128 = new MultiFormatWriter().encode("BKS-2026-000123", BarcodeFormat.CODE_128, 1, 1);
BitMatrix dm = new MultiFormatWriter().encode("(01)09506000134352", BarcodeFormat.DATA_MATRIX, 1, 1,
Map.of(EncodeHintType.DATA_MATRIX_SHAPE, SymbolShapeHint.FORCE_SQUARE));
try (PdfDocument pdf = new PdfDocument(new PdfWriter("zxing.pdf"))) {
PdfCanvas canvas = new PdfCanvas(pdf.addNewPage(PageSize.A5));
draw(canvas, qr, 40, 300, 2.5f, 2.5f);
draw(canvas, c128, 40, 220, 0.8f, 40f);
draw(canvas, dm, 40, 140, 3f, 3f);
}
java.nio.file.Files.writeString(java.nio.file.Path.of("zxing-qr.svg"), toSvg(qr));
System.out.println("qr " + qr.getWidth() + "x" + qr.getHeight() + ", c128 " + c128.getWidth() + "x" + c128.getHeight()
+ ", dm " + dm.getWidth() + "x" + dm.getHeight());
}
}
Source: ZXing project, repository and the
API documentation of QRCodeWriter, MultiFormatWriter and BitMatrix.
The points to remember about this code are the following.
-
Ask for one cell per module. Both writers take a requested width and height in pixels and scale the symbol up to at least that size using whole numbers of pixels per module. Passing
1, 1returns the exact module grid (41 by 41 for the sample QR code, which is a version 4 symbol of 33 modules plus its quiet zone of four modules on each side), and you choose the size in points when you draw. A matrix scaled to 300 or 600 pixels instead would need the same maths in reverse and invites rounding errors. -
The QR quiet zone is a hint.
QRCodeWriteradds a margin of four modules by default, which is the standard width; the hintEncodeHintType.MARGINchanges it. The example sets it explicitly, because the number lives inside the matrix.MultiFormatWriterdoes not add a margin to Data Matrix and Aztec; in the scratch tests a clean image of a Data Matrix without a margin could not be decoded by ZXing’s own reader, and one with a quiet zone of two modules or more could. Always leave white space around such a symbol on the page. -
Rows are top to bottom, PDF is bottom to top. The
drawmethod flips the row index. A 1D symbol is a matrix of one row, which is why the Code 128 call asks for height 1 and draws the bars 40 points high (moduleHof 40). -
Merge neighbouring modules.
drawemits one rectangle per horizontal run of dark modules instead of one per module, which keeps the content stream small. The SVG writer applies the same idea with a single path. A QR code withHcorrection has several hundred runs; this is nothing for a PDF, but a table of a thousand invoices with a code each benefits from the reuse of a form object (see the page-event example). -
Pick the error-correction level with the enum of ZXing. The class here is
com.google.zxing.qrcode.decoder.ErrorCorrectionLevel, and the hint type iscom.google.zxing.EncodeHintType. Both differ from the iText classes of the same names, so import them with care when a file uses both libraries. -
The Data Matrix shape hint.
EncodeHintType.DATA_MATRIX_SHAPEwithSymbolShapeHint.FORCE_SQUAREgives the 16 by 16 square symbol used above; without it ZXing chose a rectangular 32 by 8 symbol for the same text, just as iText did.
Painting colours other than black is trivial with this approach: set the fill colour on the canvas, keeping enough contrast between the modules and the background for a reader (dark modules on a light background, with the usual caution that inverted codes are not read by all scanners). A logo in the middle of a QR code is done by painting over the matrix after drawing it, with a high error-correction level, and testing with the scanner that your customers use.
The SVG route
The toSvg method writes the matrix as a single <path> in a viewBox equal to the module grid, so the SVG scales to
any size and can be placed in a web page or a PDF. iText converts SVG with SvgConverter (see
Colours, Images & SVG), but for a plain barcode painting the
rectangles directly is shorter and does not need the svg module. SVG is the right output when the same code is also
shown on a web page or sent by e-mail, and when HTML is converted to PDF with pdfHTML, as in the next section.
Okapi Barcode for what iText lacks
Okapi Barcode works like ZXing in that it computes geometry and leaves the painting to you, but its output is richer: a
Symbol has the bars as a list of Rectangle (getRectangles()), the human-readable text as TextBox objects
(getTexts()), and, for MaxiCode, getHexagons() and getTarget(). The coordinates are in the module units of the
symbol with the origin at the top left, so painting on a PdfCanvas needs the same vertical flip as above.
package demo;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import uk.org.okapibarcode.backend.Code128;
import uk.org.okapibarcode.backend.DataMatrix;
import uk.org.okapibarcode.backend.Symbol;
import uk.org.okapibarcode.graphics.Rectangle;
import java.io.IOException;
public class Okapi {
/** Okapi computes the geometry in its own units (top-left origin); we paint it as PDF rectangles. */
static void draw(PdfCanvas canvas, Symbol symbol, float x, float yTop, float scale) {
canvas.saveState().setFillColor(ColorConstants.BLACK);
for (Rectangle r : symbol.getRectangles()) {
canvas.rectangle(x + r.x * scale, yTop - (r.y + r.height) * scale, r.width * scale, r.height * scale);
}
canvas.fill().restoreState();
}
public static void main(String[] args) throws IOException {
Code128 gs1 = new Code128();
gs1.setDataType(Symbol.DataType.GS1);
gs1.setContent("[01]09506000134352[17]271231[10]LOT42"); // GS1 AIs in square brackets
DataMatrix dm = new DataMatrix();
dm.setDataType(Symbol.DataType.GS1);
dm.setContent("[01]09506000134352[10]LOT42");
try (PdfDocument pdf = new PdfDocument(new PdfWriter("okapi.pdf"))) {
PdfCanvas canvas = new PdfCanvas(pdf.addNewPage(PageSize.A5));
draw(canvas, gs1, 40, 380, 1f);
draw(canvas, dm, 40, 300, 3f);
}
System.out.println("gs1 " + gs1.getWidth() + "x" + gs1.getHeight() + " rects=" + gs1.getRectangles().size()
+ " dm " + dm.getWidth() + "x" + dm.getHeight());
}
}
Source: Okapi Barcode, repository and documentation.
Notes on the code:
-
GS1 data uses square brackets. With
setDataType(Symbol.DataType.GS1)the content is written with the application identifiers in brackets,[01]09506000134352[17]271231[10]LOT42, and Okapi inserts the function characters. It rejects malformed identifiers ([99999]abcthrows anOkapiInputExceptionin the scratch test), but a GTIN with a wrong check digit was accepted, so validate the digits of the GTIN yourself. The numbers are invented sample values in the GS1 style; a real GTIN comes from the company prefix assigned to the publisher. The ZXing reader returned the text without the brackets for the Code 128 symbol (01095060001343521727123110LOT42, the identifiers and values run together), so the code that consumes a scan must know the GS1 application identifiers to split it. -
The same symbol class does Data Matrix, Aztec, PDF417 and QR. The two-dimensional classes are
DataMatrix,AztecCode,Pdf417,QrCode,MicroQrCode,MaxiCodeandSwissQrCode; the linear ones includeCode128,Ean,Upc,Code3Of9,Codabar,Code93,DataBar14,DataBarExpandedandDataBarLimited; postal codes includeRoyalMail4State,UspsOneCode,KixCode,JapanPostandAustraliaPost. The list was read from the classes of the 0.5.6 jar. The Swiss QR-bill itself is covered by the regulated codes page of this section. -
Module width and quiet zones are properties of the symbol.
setModuleWidth,setBarHeight,setQuietZoneHorizontalandsetQuietZoneVerticalset the integer geometry in module units before callingsetContent;getWidth()andgetHeight()then give the size that your canvas code scales. The sample Data Matrix in the test was generated with 26 by 12 modules (rectangular by default), and decoded correctly once it was drawn on white paper with a margin. -
setContentthrows for invalid input. The exceptions extendOkapiException; catchOkapiInputExceptionto report an invalid GTIN or an unsupported character to the caller instead of a generic failure. -
Human-readable text. The text boxes carry the position and the string, but fonts are your business; draw them with an iText
PdfFonton the samePdfCanvasif you need the digits under the bars, or print the number as a normal paragraph under the image.
Barcodes in pdfHTML
When the invoice is produced from an HTML template (a Thymeleaf template converted with pdfHTML, as in
HTML to PDF with pdfHTML), the simplest barcode is an SVG. pdfHTML
renders an inline <svg> element and an <img> element with a data:image/svg+xml;base64 source (both were
tested for this page), and the result is vector content in the PDF. Generating the SVG with ZXing (the toSvg method
above) and inserting it into the model of the template is therefore the shortest route; no custom tag worker is needed.
package demo;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.itextpdf.html2pdf.HtmlConverter;
import java.io.FileOutputStream;
import java.io.IOException;
public class HtmlBarcodes {
public static void main(String[] args) throws IOException, WriterException {
BitMatrix qr = Zxing.qrMatrix("https://example.com/invoices/BKS-2026-000123",
com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.M);
String svg = Zxing.toSvg(qr);
String html = """
<html><head><style>
@page { size: A5; margin: 20mm; }
.qr { width: 30mm; height: 30mm; }
</style></head><body>
<h1>Invoice BKS-2026-000123</h1>
<div class="qr">%s</div>
<p><img class="qr" alt="Invoice QR code" src="data:image/svg+xml;base64,%s"/></p>
</body></html>
""".formatted(svg, java.util.Base64.getEncoder().encodeToString(svg.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
try (FileOutputStream out = new FileOutputStream("html-qr.pdf")) {
HtmlConverter.convertToPdf(html, out);
}
}
}
Source: iText Knowledge Base,
pdfHTML QR code examples, and
How to generate 2D barcode as vector image.
The code converts one HTML document with an inline SVG and an img with a data:image/svg+xml;base64 source into a
one-page PDF; both QR codes were decoded from the rendered page.
The knowledge base page shows two other techniques: registering a custom tag worker so that an element such as
<qrcode> is replaced by a BarcodeQRCode image (useful when the template author should only write the data), and
loading the barcode through an image resource retriever. Use them when the HTML is written by people who should not
handle SVG; the code above is enough for a template that the service itself fills. Two practical tips for the HTML route:
put the svg in a div with a fixed width and height in CSS (millimetres give a predictable module size), and give the
img an alt attribute for accessibility.
If the template is rendered by Thymeleaf, the SVG string is inserted unescaped with th:utext, which is safe only
because the string was produced by your own code from your own data; never insert an SVG that came from a user, because
it may contain scripts and links. The Thymeleaf side is covered by the sections of this site on templates.
Testing barcodes by decoding them
A barcode that looks right and does not scan is the worst failure, because it appears only in production. The most effective automated check is the one used for every example on this page: render the PDF to a bitmap and decode it with a reader. ZXing includes the readers, PDFBox renders the page, and the whole test is a few lines.
package demo;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
/** Renders page 1 of a PDF that holds ONE barcode and decodes it again with ZXing. */
public class RoundTrip {
static String decode(Path pdf, BarcodeFormat format) throws IOException, NotFoundException {
try (PDDocument document = Loader.loadPDF(pdf.toFile())) {
BufferedImage page = new PDFRenderer(document).renderImageWithDPI(0, 300);
BufferedImage symbol = cropToDarkPixels(page, 60);
var bitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(symbol)));
return new MultiFormatReader().decode(bitmap, Map.of(
DecodeHintType.POSSIBLE_FORMATS, List.of(format),
DecodeHintType.TRY_HARDER, Boolean.TRUE)).getText();
}
}
/** Cuts the dark pixels out and puts them on a white canvas with a wide quiet zone. */
static BufferedImage cropToDarkPixels(BufferedImage page, int pad) {
int minX = page.getWidth(), minY = page.getHeight(), maxX = 0, maxY = 0;
for (int y = 0; y < page.getHeight(); y++) {
for (int x = 0; x < page.getWidth(); x++) {
if ((page.getRGB(x, y) & 0xFF) < 128) {
minX = Math.min(minX, x); maxX = Math.max(maxX, x);
minY = Math.min(minY, y); maxY = Math.max(maxY, y);
}
}
}
int w = maxX - minX + 1, h = maxY - minY + 1;
BufferedImage out = new BufferedImage(w + 2 * pad, h + 2 * pad, BufferedImage.TYPE_INT_RGB);
var g = out.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, out.getWidth(), out.getHeight());
g.drawImage(page.getSubimage(minX, minY, w, h), pad, pad, null);
g.dispose();
return out;
}
public static void main(String[] args) throws Exception {
// args: <format> <file.pdf>, for example QR_CODE zxing-qr.pdf
System.out.println(decode(Path.of(args[1]), BarcodeFormat.valueOf(args[0])));
}
}
Source: ZXing API documentation (MultiFormatReader, DecodeHintType) and
Apache PDFBox rendering (see Signing with Apache PDFBox & EU DSS
for the library itself, which is Apache-2.0).
The method crops the page to its dark pixels and adds a white border. That step matters: a detector such as the one for
Data Matrix did not find a symbol on a full A5 page in the scratch tests, but found the same symbol after it was cropped
with a margin, so a failed decode of a whole page proves little. The test assumes one symbol per page; for a page with
several symbols, decode with GenericMultipleBarcodeReader (com.google.zxing.multi), which worked for the
Code 128, EAN-13 and QR code of the invoice header, and for the Code 39 and PDF417 of the symbologies page. The
single-symbol RoundTrip was used for the Data Matrix (iText, ZXing and Okapi) and QR examples. In a JUnit
test, assert that decode returns exactly the string that went in, and keep a golden PDF for the layout with the
CompareTool of Testing & Performance with iText. A
decode proves that the symbol is valid and has sufficient contrast at 300 dpi; it does not prove that a hand scanner will
read it at the printed size, so test the final print once with the real device.
Practical recommendations
-
Prefer vector output and check it. A bitmap barcode that is scaled by the viewer or the printer blurs and fails. Open the PDF in a text editor or inspect the objects (see How a PDF File Works): a vector barcode is a form XObject with path operators, an image is an object with
/Subtype /Image. -
Keep the quiet zone as white space, not as design. A border, a coloured box or a neighbouring line of text that eats into the quiet zone is the usual reason for a poor read rate.
-
Choose the error-correction level for the medium. Level
Mis a good default for paper; useQorHfor labels that will be scuffed, and remember that a bigger payload at a higher level makes a denser code that needs a larger module size. -
Keep payloads short. A QR code that carries an address of a few dozen characters is easy to read; embedding the whole invoice as text is not. Link to an authenticated address instead.
-
Encode non-ASCII text deliberately. State the character set (
UTF-8) with the hint when the payload has accents, and check the reader side agrees; many readers assume ISO-8859-1 without an ECI marker. -
Do not put personal data in a code that is public. The QR code of an invoice is printed and may be photographed. Use an opaque identifier or a signed link, not names or amounts that you do not want to share.
-
Regulated codes follow their own rules. For the VeriFactu, TicketBAI, EPC or Swiss QR codes, the content, size and position are prescribed by the authority; use the library of this page as the drawing tool and the regulated codes page for what to draw.
-
Reuse form objects. When the same barcode appears many times, create the
PdfFormXObjectonce; iText writes it once and references it. -
Spring Boot. The generation code is a plain method that returns a
byte[]or writes to anOutputStream; the controller and streaming patterns are in Getting Started with Spring Boot. Libraries that are stateless and thread-safe at the encoder level (ZXing’s writers) can be shared, but create a new iTextPdfDocumentand barcode object per request.
Outdated points to avoid
-
com.itextpdf.text.pdf.Barcode128,BarcodeEAN,BarcodeQRCodeof iText 5 andcom.lowagie.text.pdf.*of iText 1 and 2: replaced bycom.itextpdf.barcodes.*, whose constructors take aPdfDocument. -
barcode.createImageWithBarcode(cb, color, color), which produced anImagebound to aPdfContentByte: replaced bycreateFormXObject(...)wrapped innew Image(xObject), a vector object. -
createAwtImageand any Swing or Batik route to a bitmap barcode: unnecessary for PDF and depends on AWT. -
Treating the barcode image as raster and scaling it: a scaled bitmap blurs; draw modules as vectors.
-
Assuming the module-size overloads of every iText barcode update the bounding box: check it, as the Data Matrix and PDF417 behaviour of 9.8.0 above shows.
-
Assuming the iText
EncodeHintTypeandErrorCorrectionLevelare the ZXing classes with the same names, or that the former is anenum. -
Relying on a Barbecue or Barcode4J example from a 2000s book for new Java 21 code: those libraries are abandoned or dormant; use ZXing, Okapi or the iText module.
References
-
iText Knowledge Base, How to generate 2D barcode as vector image.
-
iText Knowledge Base, pdfHTML QR code examples.
-
iText Java API, BarcodeQRCode, Barcode128, BarcodeEAN, Barcode39, BarcodePDF417 and BarcodeDataMatrix.
-
iText licensing, AGPLv3 licence.
-
ZXing, repository and API documentation (Apache-2.0).
-
Okapi Barcode, repository (Apache-2.0).
-
Apache PDFBox, project site (Apache-2.0).
-
In this section: Barcodes & QR Codes: Fundamentals, Barcodes in JasperReports, Regulated QR Codes on Invoices & Payments, Page Events: Headers, Footers & Watermarks, HTML to PDF with pdfHTML, Colours, Images & SVG, Tables and PDF/A & PDF/UA with iText.