Barcodes in JasperReports

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.

Baseline date: 2026-10-09. A report that prints shelf labels, delivery notes or invoices usually needs a symbol that a scanner can read: a Code 128 with the SKU, an EAN-13 with the ISBN, a QR code with a URL. JasperReports 7.0.8 has two ways to put one in a band. The first is a built-in report component, with kind="component" and a child element that names the symbol type: the Barcode4J component, which covers the usual linear and two-dimensional symbologies, and the older Barbecue component. The second is an ordinary image element whose expression returns a picture that the application drew itself, for example with ZXing. This page shows all of them in the Bookshelf Billing scenario, with the v7 JRXML syntax, what each one can and cannot do, how to size a symbol inside a band, and which one to choose today.

The examples were compiled and run with Java 21 and JasperReports 7.0.8 in a plain Maven project. Each resulting PDF was rendered with PDFBox 3.0.8 and scanned with ZXing 3.5.4, so a statement that a symbol decodes means that it did. Where a symbol was only rendered and looked at, the text says so. The theory of the symbologies themselves (module, quiet zone, check digits, error correction) is in Barcodes & QR Codes: Fundamentals, and the same symbols drawn with iText are in Barcodes with iText & ZXing. The template format is the JasperReports 7 JRXML described in JRXML Report Design. Tax-regulated QR codes, such as the one on a Spanish invoice, have their own rules and are in Regulated QR Codes.

Which approach to choose

Three options exist, and the choice is mostly about maintenance and about what the symbol must contain.

  • Barcode4J component (jasperreports-barcode4j). Maintained as part of JasperReports itself, so the integration keeps working with each release, but the encoder library underneath (Barcode4J 2.1) dates from 2012 and is dormant. The QR code is the exception: it is encoded by ZXing, not by Barcode4J. It is the right choice for labels and for standard linear symbols, because everything is declared in the template and nothing needs Java code.

  • Barbecue component (jasperreports-barbecue). It wraps Barbecue 1.5-beta1, which was abandoned upstream. It exists in 7.0.8 for old templates. New templates should not use it, because Barcode4J covers the same symbologies and more.

  • ZXing as an image expression or renderer. ZXing 3.5.4 is current and actively maintained. The cost is a few lines of Java and the loss of a template that is self-contained. It is the right choice for a QR code that needs a specific version, a mask, a character set, a logo in the middle or the very latest bug fixes, and for any case where the application already builds the payload in Java, such as the regulated QR code of an invoice.

Whatever is chosen, the library licences are permissive for this use: JasperReports 7 is distributed under the LGPL, Barcode4J and ZXing under the Apache License 2.0. This is unlike iText, which is AGPL or commercial, so a report that only needs a barcode has no licensing reason to move to another library.

Maven setup

The Barcode4J component is a separate artifact from the engine and is not pulled in by jasperreports. Add it next to the PDF exporter, the fonts and the expression compiler used by the other pages of this section. ZXing is declared explicitly, even for the component, for a reason explained below.

<properties>
  <maven.compiler.release>21</maven.compiler.release>
  <jasperreports.version>7.0.8</jasperreports.version>
  <zxing.version>3.5.4</zxing.version>
</properties>

<dependencies>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports</artifactId>
    <version>${jasperreports.version}</version>
  </dependency>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports-barcode4j</artifactId>
    <version>${jasperreports.version}</version>
  </dependency>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports-pdf</artifactId>
    <version>${jasperreports.version}</version>
  </dependency>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports-fonts</artifactId>
    <version>${jasperreports.version}</version>
  </dependency>
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports-jdt</artifactId>
    <version>${jasperreports.version}</version>
  </dependency>
  <dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>${zxing.version}</version>
  </dependency>
  <dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>${zxing.version}</version>
  </dependency>
</dependencies>

What the dependency tree showed in the run: jasperreports-barcode4j 7.0.8 brings net.sf.barcode4j:barcode4j 2.1 and declares com.google.zxing:core 3.4.0 as a compile dependency. With ZXing 3.5.4 declared in the project, Maven’s nearest-wins rule resolved core to 3.5.4, and the QR code of the component rendered and decoded with it. Declaring the version that the application wants, rather than relying on the transitive one, also keeps the component and the custom ZXing code of the later sections on the same encoder. javase is only needed by the image-expression alternative, for MatrixToImageWriter.

The Maven coordinates and versions are documented on Maven Central. The rest of the project, including the Spring Boot setup, is in Getting Started with Spring Boot.

The Barcode4J component in v7 JRXML

In JasperReports 6 a barcode was a namespaced element in the jr component namespace, inside a componentElement, and the template was validated against a schema. Version 7 replaced the XML binding with Jackson, and the template has no namespace any more. A component is an element with kind="component", a position and a size, and it has one child, component, whose kind attribute names the component. For Barcode4J the names have the form barcode4j: followed by the symbology, and the value to encode is given by a codeExpression child. A v6 template that contains barcodes cannot be loaded by 7.0.8 and has to be converted with Jaspersoft Studio 7, which is finding O15 of the section’s list of outdated points. A quick way to confirm that a component kind is known is to misspell it: the engine reports the known type ids, which in the run were barcode4j:Codabar, barcode4j:Code128, barcode4j:Code39, barcode4j:DataMatrix, barcode4j:EAN128, barcode4j:EAN13, barcode4j:EAN8, barcode4j:Interleaved2Of5, barcode4j:PDF417, barcode4j:POSTNET, barcode4j:QRCode, barcode4j:RoyalMailCustomer, barcode4j:UPCA, barcode4j:UPCE and barcode4j:USPSIntelligentMail.

The following template is the shelf-label report of the Bookshelf store. Each label has the title of the book, a Code 128 with the internal SKU, an EAN-13 with the ISBN and a QR code that opens the product page. The ISBNs and SKUs are invented sample values, and shop.example.com is a reserved example domain.

<jasperReport name="book-labels" language="java" pageWidth="595" pageHeight="842"
              columnWidth="515" leftMargin="40" rightMargin="40" topMargin="40" bottomMargin="40">
  <field name="sku" class="java.lang.String"/>
  <field name="isbn13" class="java.lang.String"/>
  <field name="title" class="java.lang.String"/>
  <title height="30">
    <element kind="staticText" x="0" y="0" width="515" height="24" fontSize="16" bold="true">
      <text><![CDATA[Bookshelf shelf labels]]></text>
    </element>
  </title>
  <detail>
    <band height="70">
      <element kind="textField" x="0" y="0" width="200" height="16" bold="true">
        <expression><![CDATA[$F{title}]]></expression>
      </element>
      <element kind="component" x="0" y="18" width="200" height="45">
        <component kind="barcode4j:Code128" textPosition="bottom" moduleWidth="0.4">
          <codeExpression><![CDATA[$F{sku}]]></codeExpression>
        </component>
      </element>
      <element kind="component" x="230" y="4" width="130" height="60">
        <component kind="barcode4j:EAN13" textPosition="bottom">
          <codeExpression><![CDATA[$F{isbn13}]]></codeExpression>
        </component>
      </element>
      <element kind="component" x="400" y="4" width="60" height="60">
        <component kind="barcode4j:QRCode" errorCorrectionLevel="M" margin="0">
          <codeExpression><![CDATA["https://shop.example.com/books/" + $F{sku}]]></codeExpression>
        </component>
      </element>
    </band>
  </detail>
</jasperReport>

The template is compiled, filled and exported like any other. The record below is the data row, with the getters that the bean data source reads. The class is a plain, runnable main method.

package bookshelf;

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;

import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

public class BarcodeReport {

    public record Label(String sku, String isbn13, String title) {
        public String getSku() { return sku; }
        public String getIsbn13() { return isbn13; }
        public String getTitle() { return title; }
    }

    public static void main(String[] args) throws Exception {
        JasperReport report;
        try (InputStream in = BarcodeReport.class.getResourceAsStream("/reports/labels.jrxml")) {
            report = JasperCompileManager.compileReport(in);
        }
        List<Label> labels = List.of(
            new Label("BK-000123", "9780306406157", "Distributed Systems Primer"),
            new Label("BK-000124", "9780131103627", "Practical Algebra"));
        JasperPrint print = JasperFillManager.fillReport(
            report, new HashMap<String, Object>(), new JRBeanCollectionDataSource(labels));
        Files.createDirectories(Path.of("target/out"));
        JasperExportManager.exportReportToPdfFile(print, "target/out/labels.pdf");
    }
}

In the run, the PDF had one page, and a ZXing scan of the rendered page found the two Code 128 symbols with the two SKUs, the two EAN-13 symbols with the two ISBNs and the two QR codes with the two URLs. The pieces that the page relies on are documented in the Barcode4J sample and the engine’s generic compile and export calls in JasperReports Architecture & Lifecycle.

Symbologies and their attributes

The component classes of 7.0.8 define the attributes below. All the component kinds share the codeExpression child and the evaluation attributes of every component (evaluationTime, and evaluationGroup when the time is Group), which matter when the code depends on a variable such as a report total that is only known at the end of the report. All linear and 2D kinds except the QR code also share the Barcode4J attributes orientation (up, left, down, right), moduleWidth, textPosition (none, bottom, top), quietZone, verticalQuietZone and a patternExpression child, which formats the human-readable text.

Component kind Symbology and typical use Attributes of its own

barcode4j:Code128

Code 128, compact alphanumeric labels and shipping references. Chosen for the SKU.

none beyond the shared ones

barcode4j:Code39

Code 39, older industrial and library labels, upper-case letters, digits and a few symbols.

checksumMode, displayChecksum, displayStartStop, extendedCharSetEnabled, intercharGapWidth, wideFactor

barcode4j:EAN13, barcode4j:EAN8

EAN-13 and EAN-8. ISBN-13 is an EAN-13 whose prefix is 978 or 979.

checksumMode

barcode4j:UPCA, barcode4j:UPCE

UPC-A (12 digits) and UPC-E (compressed), North American retail.

checksumMode

barcode4j:Codabar, barcode4j:Interleaved2Of5, barcode4j:EAN128

Codabar, Interleaved 2 of 5 and GS1-128 (EAN-128, with application identifiers).

checksumMode and symbology-specific settings

barcode4j:DataMatrix

Data Matrix, compact 2D symbol for small parts.

shape, minSymbolWidth, maxSymbolWidth, minSymbolHeight, maxSymbolHeight

barcode4j:PDF417

PDF417, stacked linear 2D symbol, used on boarding passes and some official documents.

minColumns, maxColumns, minRows, maxRows, widthToHeightRatio, errorCorrectionLevel (0 to 8)

barcode4j:POSTNET, barcode4j:USPSIntelligentMail, barcode4j:RoyalMailCustomer

Four-state postal codes (the USPS ones are US postal symbologies).

ascenderHeight, checksumMode, intercharGapWidth, trackHeight

barcode4j:QRCode

QR code, encoded and rendered with ZXing.

errorCorrectionLevel (L, M, Q, H), margin, qrVersion

The table is read from the compiled classes of the 7.0.8 artifact, so attribute names are the ones that Jackson binds in a v7 template. Values such as the checksumMode strings (auto, check, add, ignore) are those of Barcode4J. The JasperReports engine’s own documentation of the components is the Barcode4J sample page, and the upstream project describes every symbology in the Barcode4J documentation and on its SourceForge project page. The QR code has been rendered with ZXing since JasperReports 6.0.2, before which the component had no QR code.

A second run exercised the other kinds in one template, one element per symbol, with the attributes in the table where relevant. The Code 128, Code 39, EAN-8 and UPC-A symbols decoded with the expected values. The Data Matrix, PDF417, POSTNET and USPS Intelligent Mail symbols rendered and were checked by eye on the page image, but were not scanned, so nothing here claims they are readable. The POSTNET and USPS symbols are US postal formats. The USPS Intelligent Mail symbol expects a 20, 25, 29 or 31 digit string, and the engine accepted a 31 digit sample value.

Check digits and invalid values

A barcode that is wrong is worse than no barcode, so the behaviour on bad input is worth knowing. In the run:

  • An EAN-13 value of twelve digits was accepted and the symbol decoded with the thirteenth digit, the check digit, added by the encoder.

  • An EAN-13 with thirteen digits and a wrong last digit stopped the fill with a JRRuntimeException whose cause was IllegalArgumentException: Checksum is bad (8). Expected: 7. A value with a letter stopped it as well, with Invalid characters found. Valid are 0-9 only. The whole report fails, not just the label, so validate the code in Java before the data reaches the template if one bad row must not stop a batch.

  • A null code expression produced an empty area and no error. The page rendered and no symbol was found on it.

  • An empty string for the QR code produced a valid QR code that decodes to an empty text. Use a printWhenExpression on the element, or return null for blank values in the expression, if an empty symbol must not be printed.

The checksumMode attribute decides what happens with the check digit for the symbologies that have one. With auto the encoder uses the default of the symbology, with check it verifies a supplied digit and with add it computes one.

Sizing a symbol in a band

A barcode component is drawn by Barcode4J at a size that comes from the symbol and from moduleWidth, then placed in the element rectangle. The element’s width and height therefore do not stretch the bars. This is the main source of surprises, so the points below are the result of rendering the same Code 128 in elements of different sizes:

  • The symbol is not stretched to the element. A Code 128 of nine characters in an element 300 points wide was drawn at its natural size, about 55 points wide, at the left of the element, and a larger box did not make the bars wider. The width comes from the number of characters and from moduleWidth. The unit of moduleWidth and its exact scaling were not worked out in the run (values of 0.5 and the default gave symbols of similar width), so tune it by trial.

  • A module that is too small is unreadable. A moduleWidth of 0.1 rendered as a grey smear about 11 points wide on the page image. The minimum module depends on the symbology’s standard, the printer resolution and the scanner, so test with the real printer and scanner before fixing a label design.

  • Height is the element height minus the text. With textPosition="bottom" the human-readable text takes part of the height, and with none all of it goes to the bars, so make the element high enough for the scanner in use.

  • Quiet zone. quietZone and verticalQuietZone are the white margins in modules, and the QR margin is in modules. The page itself is white, so a symbol placed on white paper has a quiet zone as long as no other element is within it. Never put a coloured background, a line or a text field in the margin of the symbol. The QR code of the labels above used margin="0" and still decoded, because the surrounding area was empty.

  • Orientation. orientation="left" rotates the symbol by 90 degrees to the left. The element rectangle is not rotated with it, so the element must be tall instead of wide, and the symbol was drawn across the full width of a wide element in the run. Allow for it when laying out a vertical label.

  • Two-dimensional symbols. A QR code and a Data Matrix are square. Give them a square element. In a non-square one the symbol follows the smaller side, so extra space is empty. A PDF417 in an element of 55 points of height was squashed into a dense block. Set minColumns, maxColumns and widthToHeightRatio so that the aspect ratio of the symbol matches the element.

The overall rule for a band is to size the symbol first, using moduleWidth and the number of characters, then set the element rectangle to that size plus the quiet zone, and finally make the band high enough for the element. For a fixed grid of labels, such as sheets of stickers, a detail band of fixed height, as described in JRXML Report Design, is simpler than a stretching band.

Vector or raster: the image producer

Barcode4J does not draw into the PDF directly. An image producer turns the symbol into an image, and the default is the SVG producer. The effect on a PDF is visible: in the run, the PDF of the label report had no image XObject on the page, so the bars are vector paths, sharp at any zoom, and the file is small. The raster producer is selected by a property, which can be set globally in jasperreports.properties, per report as a property element, or on a single element:

<jasperReport name="size" language="java" pageWidth="595" pageHeight="842">
  <property name="net.sf.jasperreports.components.barcode4j.image.producer" value="image"/>
  ...
</jasperReport>

With the value image the same template produced one raster image per symbol: three images of between 96 and 327 pixels in width and 167 in height for three Code 128 elements, at the default resolution. The resolution is set by net.sf.jasperreports.components.barcode4j.image.resolution (300 by default), and the properties ...image.gray and ...image.antiAlias (both true by default) control the colour model and the smoothing. Anti-aliasing a bar code is harmful for a scanner, because the edge of a bar becomes grey. Prefer the default SVG producer for PDF output, and use the raster producer only for an exporter that cannot draw vectors. The QR code has its own producer property, net.sf.jasperreports.components.barcode4j.qrcode.producer.{alias}, and the character set of its payload is net.sf.jasperreports.components.qrcode.character.encoding, whose default in 7.0.8 is UTF-8. All of them are listed in the configuration reference.

The Barbecue component

The jasperreports-barbecue artifact wraps Barbecue 1.5-beta1, a project that has been abandoned upstream. Its coordinates are net.sf.jasperreports:jasperreports-barbecue with the same version as the engine, and it works in 7.0.8, but with a different, older template structure. In v7 JRXML its kind has no symbology suffix: the symbology is the type attribute.

<element kind="component" x="0" y="0" width="200" height="60">
  <component kind="barbecue" type="Code128" drawText="true" barWidth="1" barHeight="40">
    <codeExpression><![CDATA[$F{sku}]]></codeExpression>
  </component>
</element>

With jasperreports-barbecue on the classpath the template compiled and filled, the PDF had a vector Code 128 and ZXing decoded it. The attribute names come from the component class, which also has checksumRequired, rotation and an applicationIdentifierExpression for GS1-128. Two other variants, barbecue:Code128 and barbecue:barbecue, were refused with the unresolved type id error shown above. The component’s types are those of the Barbecue library (Code 128 A, B and C, Code 39, Codabar, EAN-13, UPC-A, 2 of 7 and others), and it has no QR code, no Data Matrix and no PDF417. Because the upstream project is not maintained, the page recommends it only for a template that already uses it. The upstream projects are on SourceForge, and the engine’s Barbecue sample shows the component.

A maintained alternative: ZXing as an image expression

The image element prints what its expression returns, and the expression may return a java.awt.Image, a path or URL string, an input stream, or a net.sf.jasperreports.renderers.Renderable. A static helper that draws a QR code with ZXing 3.5.4 is therefore enough, and the template stays free of any component. This is the pattern for the QR code of an invoice, where the payload is built in Java, and also for the formats the Barcode4J component lacks, such as the Aztec code, which ZXing writes as well.

package bookshelf;

import java.awt.image.BufferedImage;
import java.util.Map;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/** Static helpers called from JRXML expressions. */
public final class ZxingImages {

    private ZxingImages() {
    }

    public static BufferedImage qr(String text, int sizePx) throws WriterException {
        Map<EncodeHintType, Object> hints = Map.of(
            EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M,
            EncodeHintType.MARGIN, 0,
            EncodeHintType.CHARACTER_SET, "UTF-8");
        BitMatrix matrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, sizePx, sizePx, hints);
        return MatrixToImageWriter.toBufferedImage(matrix);
    }
}

The class is made visible to the expressions with an import element, whose content is the class name in v7 JRXML (an import element with a value attribute, the form of the v6 import declaration, is not read: the compiler reported Syntax error on token "import" and the template did not compile). The image element declares the expression class so that the compiler knows the type:

<jasperReport name="zxing-qr" language="java" pageWidth="595" pageHeight="842"
              columnWidth="515" leftMargin="40" rightMargin="40" topMargin="40" bottomMargin="40">
  <import>bookshelf.ZxingImages</import>
  <field name="sku" class="java.lang.String"/>
  <field name="title" class="java.lang.String"/>
  <detail>
    <band height="90">
      <element kind="textField" x="0" y="0" width="200" height="16" bold="true">
        <expression><![CDATA[$F{title}]]></expression>
      </element>
      <element kind="image" x="230" y="4" width="80" height="80" scaleImage="FillFrame" onErrorType="Error">
        <expression class="java.awt.Image"><![CDATA[ZxingImages.qr("https://shop.example.com/books/" + $F{sku}, 400)]]></expression>
      </element>
    </band>
  </detail>
</jasperReport>

Three details matter. The size in pixels is the resolution of the bitmap, not the printed size: 400 pixels in an 80 point frame is a bit more than 360 dpi, which is plenty. scaleImage="FillFrame" stretches the bitmap to the frame, and since the matrix was generated square and without margin, a square frame keeps the modules square. And the expression runs once per row, in the fill, so a heavy encoder should not be used for thousands of rows without a cache.

In the run, the PDF page contained one 400 by 400 raster image per row, the exporter embedded the bitmap as it was, and the QR codes decoded with the SKU URLs. Drawn at a larger size the edges of such a bitmap blur in a viewer. The next section avoids it.

A vector QR code with a Graphics2D renderer

A Renderable is the engine’s abstraction of something that can be drawn into an image slot, and the Graphics2DRenderable interface is the form that draws into a Java2D Graphics2D at the rectangle that the layout gives it. The PDF exporter draws such a renderable as vector graphics. A renderer that draws every dark module of a QR code as a rectangle gives a QR code that is sharp at any zoom and costs no bitmap. ZXing’s Encoder is used directly to get the module matrix without the quiet zone.

package bookshelf;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.Map;

import com.google.zxing.EncodeHintType;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.Encoder;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.renderers.AbstractRenderToImageAwareRenderer;
import net.sf.jasperreports.renderers.Graphics2DRenderable;

/** Vector QR code: every dark module is drawn as a rectangle, so the PDF stays sharp at any zoom. */
public class QrRenderer extends AbstractRenderToImageAwareRenderer implements Graphics2DRenderable {

    private static final long serialVersionUID = 1L;

    private final ByteMatrix matrix;

    public QrRenderer(String text) throws Exception {
        this.matrix = Encoder.encode(text, ErrorCorrectionLevel.M,
            Map.of(EncodeHintType.CHARACTER_SET, "UTF-8")).getMatrix();
    }

    @Override
    public void render(JasperReportsContext context, Graphics2D g, Rectangle2D rectangle) throws JRException {
        double module = Math.min(rectangle.getWidth(), rectangle.getHeight()) / matrix.getWidth();
        Path2D.Double dark = new Path2D.Double(Path2D.WIND_NON_ZERO);
        for (int y = 0; y < matrix.getHeight(); y++) {
            for (int x = 0; x < matrix.getWidth(); x++) {
                if (matrix.get(x, y) == 1) {
                    dark.append(new Rectangle2D.Double(
                        rectangle.getX() + x * module, rectangle.getY() + y * module, module, module), false);
                }
            }
        }
        g.setColor(Color.BLACK);
        g.fill(dark);
    }
}

The base class of the renderer is AbstractRenderToImageAwareRenderer in 7.0.8. A source that names AbstractRenderToImageAwareRenderable is from a version before 7 and does not compile. The template places the renderer with an expression of the class net.sf.jasperreports.renderers.Renderable:

<import>bookshelf.QrRenderer</import>
...
<element kind="image" x="400" y="4" width="80" height="80" scaleImage="RetainShape" onErrorType="Error">
  <expression class="net.sf.jasperreports.renderers.Renderable"><![CDATA[new QrRenderer("https://shop.example.com/books/" + $F{sku})]]></expression>
</element>

A first version of the renderer filled one rectangle per module, one fill call each. The page rendered, and it looked right, but ZXing could not decode the QR code, because the anti-aliased edges of adjacent rectangles left hairline seams between modules at 300 dpi. Collecting all the modules into one Path2D and filling it once, as in the listing, made the symbol decode. This is worth remembering for any renderer that draws a symbol from many shapes: draw the dark area as one path. With that change, the PDF page of the run contained no image XObject for the renderer, only the bitmap of the previous element, and the QR code decoded with the SKU URL.

The matrix returned by Encoder has no quiet zone, so the element must be surrounded by white space, at least four modules wide, as the QR standard requires. The listing is the minimal version. To add a logo, reduce the symbol with level H, draw the logo in the centre area and verify with a scanner that the result still decodes. The background, a custom foreground colour or rounded modules are all one-line changes in render, but contrast between modules and background is what a scanner needs, so a dark module colour on a light background is the safe choice. The API of the interface is in the JasperReports API documentation, and the ZXing library on the ZXing GitHub project with the API documentation.

Data to put in the symbol

The component and the image expression solve the drawing. What goes into the symbol is a separate decision.

  • A shelf label needs a short, stable identifier such as the SKU. The scanner finds the record in the database, so the symbol should not repeat the title or the price.

  • A product page link is a URL. A longer payload needs a higher QR version, a larger symbol or more modules, and a payload of about 50 characters is comfortably readable at 20 millimetres.

  • An invoice QR code carries a fixed structure that an authority defines. Its payload, error-correction level, size and position are rules, not choices, and are described in Regulated QR Codes. Build the payload in Java, with a unit test that parses it back, and pass the string to the template as a parameter.

  • Do not put personal data in a QR code on a document that may be photographed or forwarded. The symbol is readable by anybody with a phone, and a QR code cannot be revoked like a link.

A report that has a URL in the symbol often also prints it as text, for people who cannot scan it. A textField with hyperlinkType="Reference" also makes the printed text a clickable link in the PDF, as described in Hyperlinks, Bookmarks & Report Books.

Spring Boot service notes

In the Spring Boot service of the scenario, the template is compiled once at start-up or at build time, as in Spring Boot Integration, and filled per request. Three consequences for barcodes follow.

  • The jasperreports-barcode4j artifact must be on the runtime classpath. If it is not, compilation fails with Jackson’s Could not resolve type id error and a list of known ids that does not contain the barcode4j: kinds. The message does not name the missing artifact.

  • An import in the template (<import>bookshelf.ZxingImages</import>) is resolved against the application’s classpath, so a helper class can be a Spring-managed class only if the method called is static, or if the instance is passed as a report parameter. A static helper with no state is the simplest.

  • Because expressions are code, the usual care about untrusted templates applies, as in Security Hardening. A helper that encodes whatever string the template gives it does not add a risk, but one that fetches a URL, reads a file or logs its argument does.

When the labels are only a part of a larger document, the PDF produced can be post-processed. A filled form or a signature is added after the export, as in Signing with iText. For a signed document, the symbol is part of the content that is signed, so it must be drawn before the signature, never after.

Outdated points to avoid

Older books and tutorials on JasperReports describe barcodes in ways that no longer apply to 7.0.8.

  • A barcode as a componentElement in the jr namespace, validated against the DTD or an XSD, is the v6 format. A JasperReports 7 template has no namespace and uses kind="component" with a component child (finding O15). Templates in the old format must be converted with Jaspersoft Studio 7, because 7.0.8 does not load them.

  • Older recipes draw a barcode with a barcode font, a scriptlet or the Barbecue classes in an image expression. The Barcode4J component or ZXing make these unnecessary, and the Barbecue library has been abandoned upstream.

  • Examples written for Java 1.4 to 8 use raw types and anonymous classes. The examples here are Java 21 (finding O28), with a record for the data and the Jakarta-era dependencies of JasperReports 7.

  • Advice to render a barcode as a bitmap at low resolution to save space is obsolete: the default SVG producer gives a small, sharp vector symbol in the PDF.

Limits of this page

The Code 128, Code 39, EAN-8, EAN-13, UPC-A and QR symbols, the Barbecue Code 128 and both ZXing alternatives were scanned back from the rendered PDF. The Data Matrix, PDF417, POSTNET and USPS Intelligent Mail symbols were rendered and inspected visually only. The scan was done on the PDF rendered at 300 dpi on a screen, which says nothing about a thermal printer, a laser printer, a label’s material or a particular scanner. Verify each symbology in the real printing chain before a label design is approved. The product of this page is a template that is correct, not a guarantee that a given symbol is readable in a warehouse.