ZXing 3.5.4 Deep Dive: New Features, Barcode Support & Java Code Examples

This article explores ZXing 3.5.4's Java library updates including grayscale luminance source, QRCodeMultiReader metadata preservation, PDF417 fixes, and improved barcode recognition, with practical code examples for generating and reading QR codes and other barcode formats.

java1234
java1234
java1234
ZXing 3.5.4 Deep Dive: New Features, Barcode Support & Java Code Examples

What ZXing Looks Like in Your Project

ZXing (Zebra Crossing) is a Google-originated, Apache 2.0 library widely used on the server side for barcode generation and recognition. Two Maven artifacts are typically required: core — handles the barcode logic: encoding text into a matrix or decoding a bit matrix into a string. It has no dependency on JPG, PNG, or AWT. javase — bridges to BufferedImage and provides command-line tools. If you only manipulate matrices in memory and render them yourself, you can skip this module.

The typical workflow is two-sided: you give text and get a barcode image, or you give an image and get the decoded content, format, and position. Batch ticket printing, waybill scanning, and test round-trips (generate then read back) all follow this pattern. The Java edition is designed for "image already in hand" scenarios; real-time camera scanning is better served by platform-specific APIs like ML Kit.

Barcode Formats Covered

Beyond QR Code, ZXing supports a broad set of symbologies:

2D / stacked: QR Code, Aztec, Data Matrix, PDF417, MaxiCode

Retail: EAN-8, EAN-13, UPC-A, UPC-E

Logistics / industrial: Code 39, Code 93, Code 128, ITF (Interleaved 2 of 5), Codabar, RSS-14, RSS Expanded

Version 3.5.0 (May 2022) overhauled Aztec, PDF417, and Data Matrix decoding and added a more compact minimal encoding mode. Subsequent 3.5.1–3.5.3 releases addressed edge cases: QR Code no longer hangs on two extreme images, Code 128 dropped an excessive length limit, GS1 Application Identifiers were updated to the 2023 spec, and RSS Expanded false positives were reduced. Version 3.5.4 continues this bug-fix trajectory.

How an Image Gets Decoded

The decode pipeline consists of four steps:

Luminance source — converts color or grayscale to 0–255 luminance values (0 = black, 255 = white).

Binarization — decides whether each pixel is black or white. HybridBinarizer is robust for photos; GlobalHistogramBinarizer can be faster on clean, near-pure barcode images.

Format hints — restrict the symbologies to try. In a waybill scenario you likely only need Code 128 and QR Code; narrowing via POSSIBLE_FORMATS improves both speed and accuracy.

Try harder — TRY_HARDER spends more time on difficult images (blurry, rotated). It is unnecessary for clean electronic codes.

What Changed in 3.5.4

No new symbologies were added. The release notes look fragmented, but each item removes a recurring pain point.

Grayscale images can be fed directly

New GrayscaleLuminanceSource(width, height, pixels) accepts a row-major byte array where each byte is a luminance value. If your image is already grayscale, you no longer need to wrap it in an RGB layer. RGBLuminanceSource became a subclass of it and gained a counter-clockwise 90° rotation — useful for landscape phone photos — plus an early array-length check (width × height) that fails fast instead of throwing an array-index exception later.

Multi-QR results retain symbology identifiers

QRCodeMultiReader

previously dropped the SYMBOLOGY_IDENTIFIER metadata, making its output inconsistent with single-code QRCodeReader. 3.5.4 preserves this field in result.getResultMetadata(), which matters for GS1 integration or when distinguishing multiple codes on the same image.

Two PDF417 pits filled

Text compaction mode ( Compaction.TEXT) now strictly accepts only code points 0–127 and throws a clear exception for out-of-range characters. Earlier versions could silently produce a visually normal but semantically wrong barcode.

Specifying row/column bounds no longer spuriously throws "Unable to fit message in columns" when the content actually fits. The sizing calculation (issue 1831) has been corrected so increasing the minimum column count no longer magically unblocks rendering.

Slightly more stable 1D recognition

RSS Expanded removed an inefficient container operation, reducing overhead during batch scans.

ITF (Interleaved 2 of 5) relaxed the 3× module-width error tolerance, making slightly skewed prints readable.

QR Code module-count estimation now enforces the mathematical rule that a valid side length ≡ 1 (mod 4). Previously only off-by-1 errors were auto-corrected; off-by-2 gave up. 3.5.4 subtracts 2 to reach a legal size. Distortion calculation for the three finder patterns forming an isosceles right triangle was also fixed.

Stricter email parsing

When a decode result is interpreted as an email address, format validation is tightened so random strings are less likely to be misclassified as mailto links.

These are not headline features, but they matter for high-volume batch jobs: fewer array-bound exceptions, fewer "PDF417 won't render" mysteries, and preserved metadata when multiple QR codes share an image.

Maven Dependencies

<dependency>
  <groupId>com.google.zxing</groupId>
  <artifactId>core</artifactId>
  <version>3.5.4</version>
</dependency>
<dependency>
  <groupId>com.google.zxing</groupId>
  <artifactId>javase</artifactId>
  <version>3.5.4</version>
</dependency>

All three modules share the same version number to avoid core/javase mismatches. The library runs on Java 8+.

Example: Generate a Ticket QR Code

The following writes ticket-qr.png. Error correction level M tolerates some dirt or creases. A 1-module quiet zone (margin) is kept for reliable printing and display. Character set is forced to UTF-8 so Chinese characters survive the encoding step.

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.decoder.ErrorCorrectionLevel;
import com.google.zxing.MultiFormatWriter;

import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

/**
 * Ticket QR code generation example.
 */
public class TicketQrWriter {

    /**
     * Write ticket info to a QR image.
     * @param file output PNG path
     */
    public static void write(Path file) throws WriterException, IOException {
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
        hints.put(EncodeHintType.MARGIN, 1);

        String content = "门票:A-1024,入场:2026-11-02 17:25:17";
        BitMatrix matrix = new MultiFormatWriter().encode(
                content,
                BarcodeFormat.QR_CODE,
                360,
                360,
                hints);
        MatrixToImageWriter.writeToPath(matrix, "PNG", file);
    }
}

Switch to BarcodeFormat.CODE_128 with a narrow rectangle for logistics 1D barcodes. Use BarcodeFormat.PDF_417 for PDF417. If you explicitly set Compaction.TEXT and the content contains non-ASCII characters (e.g., Chinese), 3.5.4 will reject it immediately — fall back to auto or byte compaction, or ensure the payload is pure alphanumeric.

Do not set margin to 0 to make the image smaller. The surrounding white space (quiet zone) is part of the spec; removing it often yields a barcode that generates but fails to scan.

Example: Read the QR Code Back

Decoding usually pins the format because the business context already knows which symbology to expect. Trying all formats works but is slower and more error-prone.

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.GrayscaleLuminanceSource;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;

/**
 * Read QR code from image.
 */
public class TicketQrReader {

    /**
     * Read QR text from PNG. Returns null if not found.
     * @param file image to decode
     * @return barcode text; null for blurry, missing quiet zone, or wrong format
     */
    public static String read(Path file) throws IOException {
        BufferedImage image = ImageIO.read(file.toFile());
        LuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
        hints.put(DecodeHintType.POSSIBLE_FORMATS,
                Collections.singletonList(BarcodeFormat.QR_CODE));
        hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);

        try {
            Result result = new MultiFormatReader().decode(bitmap, hints);
            return result.getText();
        } catch (ReaderException ex) {
            // Not found, checksum failure, format mismatch — all treated as no-read
            return null;
        }
    }
}
CHARACTER_SET

is critical for 1D barcodes (QR codes usually embed encoding info). To read multiple QR codes at once, swap MultiFormatReader for QRCodeMultiReader; in 3.5.4 each result's SYMBOLOGY_IDENTIFIER is preserved inside result.getResultMetadata().

If the image is already a grayscale byte array, use the new source directly. The array length must equal width × height.

/**
 * Decode from grayscale pixels, optionally rotating 90° counter-clockwise first.
 */
public class GrayTicketDecoder {

    /**
     * @param pixels row-major grayscale values, 0 = black, 255 = white
     * @param rotate if true, rotate 90° CCW before decoding
     */
    public static LuminanceSource toSource(byte[] pixels, int width, int height, boolean rotate) {
        LuminanceSource source = new GrayscaleLuminanceSource(width, height, pixels);
        if (rotate) {
            source = source.rotateCounterClockwise();
        }
        return source;
    }
}

Treat decode failures as a separate business branch — most are due to blur, glare, format mismatch, or missing quiet zone. Logging them as system exceptions makes later debugging harder.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaQR codeZXingbarcode3.5.4grayscale decodingMultiFormatReaderPDF417
java1234
Written by

java1234

Former senior programmer at a Fortune Global 500 company, dedicated to sharing Java expertise. Visit Feng's site: Java Knowledge Sharing, www.java1234.com

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.