Java Barcode & QR Code Apps: Command-Line, Swing GUI, and Spring Boot Web with Dynamsoft and ZXing

With Dynamsoft Barcode Reader 11.x and ZXing 3.5.3, you can build three kinds of Java barcode & QR code readers — a command-line tool, a Swing desktop GUI, and a Spring Boot web service — from a single code base. On a test sheet containing 14 different 1D/2D barcodes, Dynamsoft detects all 14 codes (EAN-8, GS1 DataBar, UPC-A, Aztec, Code 128, Data Matrix, QR Code, Industrial 2 of 5, PDF417, Code 93, ITF, Code 39 Extended, EAN-13, Codabar) in 107 ms, while ZXing finds 3 (Codabar, Code 39, QR Code) in 208 ms.

What You’ll Build

  • A Maven-based command-line barcode scanner that compares both engines side-by-side
  • A Swing GUI app with a file chooser and an engine selector
  • A Spring Boot 3 REST API (POST /api/dynamsoft, POST /api/zxing) with Swagger UI for in-browser testing

Key Takeaways

  • Dynamsoft Barcode Reader 11.x replaces the legacy BarcodeReader class with CaptureVisionRouter; a single capture(...) call returns a DecodedBarcodesResult containing every barcode in the image.
  • The same capture API accepts a file path, a byte[], or an ImageData object, so CLI, GUI, and web apps share one decode path.
  • ZXing requires manual conversion from BufferedImage to BinaryBitmap and only decodes one symbology family at a time unless you wrap it with GenericMultipleBarcodeReader; even then, it found 3 of 14 barcodes on our test sheet.
  • Spring Boot 3 requires springdoc-openapi-starter-webmvc-ui (not the legacy springdoc-openapi-ui) for Swagger UI.

Prerequisites

  • JDK 8+ (tested on OpenJDK 17)
  • Maven 3.6+
  • Get a 30-day free trial license for Dynamsoft Barcode Reader — set it via the DBR_LICENSE_KEY environment variable or paste it into the code

Test Image

The test image below contains 14 barcodes of different symbologies.

multi-barcode test sheet

Step 1: Configure Maven

Add the Dynamsoft Maven repository and the two dependencies to pom.xml. All three sample apps share this setup:

<repositories>
    <repository>
        <id>dbr</id>
        <url>https://download2.dynamsoft.com/maven/dbr/jar</url>
    </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>com.dynamsoft</groupId>
        <artifactId>dbr</artifactId>
        <version>11.6.1000</version>
    </dependency>
    <dependency>
        <groupId>com.google.zxing</groupId>
        <artifactId>core</artifactId>
        <version>3.5.3</version>
    </dependency>
</dependencies>

Step 2: Build the Command-Line Scanner

Import both SDKs

import com.dynamsoft.core.EnumErrorCode;
import com.dynamsoft.cvr.CaptureVisionRouter;
import com.dynamsoft.cvr.CapturedResult;
import com.dynamsoft.cvr.EnumPresetTemplate;
import com.dynamsoft.dbr.BarcodeResultItem;
import com.dynamsoft.dbr.DecodedBarcodesResult;
import com.dynamsoft.license.LicenseError;
import com.dynamsoft.license.LicenseManager;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.multi.GenericMultipleBarcodeReader;

Decode with ZXing

ZXing needs a manual BufferedImageint[]RGBLuminanceSourceBinaryBitmap conversion before decoding:

BufferedImage image = ImageIO.read(new File(filename));
int[] pixels = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
RGBLuminanceSource source = new RGBLuminanceSource(image.getWidth(), image.getHeight(), pixels);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

MultiFormatReader reader = new MultiFormatReader();
GenericMultipleBarcodeReader multiReader = new GenericMultipleBarcodeReader(reader);

try {
    Result[] zxingResults = multiReader.decodeMultiple(bitmap);
    System.out.println("ZXing result count: " + zxingResults.length);
    for (Result r : zxingResults) {
        System.out.println("Format: " + r.getBarcodeFormat());
        System.out.println("Text: " + r.getText());
    }
} catch (NotFoundException e) {
    System.out.println("ZXing found no barcode.");
}

Decode with Dynamsoft

With Dynamsoft Barcode Reader 11.x, decoding is three steps: init the license once, create a CaptureVisionRouter, then call capture with the preset PT_READ_BARCODES template:

String licenseKey = System.getenv().getOrDefault("DBR_LICENSE_KEY", "LICENSE-KEY");
LicenseError licenseError = LicenseManager.initLicense(licenseKey);
if (licenseError.getErrorCode() != EnumErrorCode.EC_OK) {
    System.out.println("License failed: " + licenseError.getErrorString());
    return;
}

CaptureVisionRouter cvRouter = new CaptureVisionRouter();
long start = System.currentTimeMillis();
CapturedResult result = cvRouter.capture(filename, EnumPresetTemplate.PT_READ_BARCODES);
long elapsed = System.currentTimeMillis() - start;

DecodedBarcodesResult barcodeResult = result.getDecodedBarcodesResult();
BarcodeResultItem[] items = barcodeResult != null ? barcodeResult.getItems() : null;
System.out.println("Dynamsoft result count: " + items.length + " (in " + elapsed + " ms)");
for (BarcodeResultItem item : items) {
    System.out.println("Format: " + item.getFormatString());
    System.out.println("Text: " + item.getText());
}

Package as a runnable JAR

To run the program conveniently as a single JAR, use the maven-assembly-plugin with the jar-with-dependencies descriptor:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
            <manifest>
                <mainClass>com.java.barcode.App</mainClass>
            </manifest>
        </archive>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals><goal>single</goal></goals>
        </execution>
    </executions>
</plugin>

Build and run

mvn clean package
set DBR_LICENSE_KEY=your-trial-license-key
java -jar target/test-1.0-SNAPSHOT-jar-with-dependencies.jar AllSupportedBarcodeTypes.png

java barcode command line output

ZXing returns 3 results in 208 ms, whereas Dynamsoft Barcode Reader returns 14 results in 107 ms — including the Aztec, PDF417, Data Matrix, and GS1 DataBar codes that ZXing misses on this sheet.

Step 3: Build the Swing Desktop GUI

The GUI wraps the same decode methods in a JPanel with a file chooser, an engine selector, and a scrollable result area.

Widgets

  • JTextArea — display decoding results
  • JButton + JFileChooser — pick an image from disk
  • JComboBox — toggle between ZXing and Dynamsoft

Layout

public App() {
    super(new BorderLayout());

    mFileChooser = new JFileChooser();
    mFileChooser.setFileFilter(new FileNameExtensionFilter(".png", "png"));
    mLoad = new JButton("Load File");
    mLoad.addActionListener(this);

    mSourceList = new JComboBox(new String[]{"ZXing", "Dynamsoft"});
    mSourceList.setSelectedIndex(0);

    JPanel buttonPanel = new JPanel();
    buttonPanel.add(mSourceList);
    buttonPanel.add(mLoad);
    add(buttonPanel, BorderLayout.PAGE_START);

    mTextArea = new JTextArea();
    add(new JScrollPane(mTextArea), BorderLayout.CENTER);
}

Click handler

When the user picks a file, dispatch to the engine selected in the combo box:

@Override
public void actionPerformed(ActionEvent e) {
    int returnVal = mFileChooser.showOpenDialog(App.this);
    if (returnVal == JFileChooser.APPROVE_OPTION) {
        String filename = mFileChooser.getSelectedFile().toPath().toString();
        if (mSourceList.getSelectedItem().toString().equals("Dynamsoft")) {
            BarcodeResultItem[] items = decodeFileDynamsoft(filename);
            // append items to mTextArea
        } else {
            Result[] results = decodefileZXing(filename);
            // append results to mTextArea
        }
    }
}

The Dynamsoft path inside the GUI is identical to the CLI: create a CaptureVisionRouter, call capture(filename, EnumPresetTemplate.PT_READ_BARCODES), and walk the returned BarcodeResultItem[]. You can also pass an image path as a program argument to auto-run both engines on startup, which is what the screenshot below shows.

mvn clean package
java -jar target/test-1.0-SNAPSHOT-jar-with-dependencies.jar AllSupportedBarcodeTypes.png

java barcode swing gui comparing zxing and dynamsoft

The window above shows the side-by-side comparison: ZXing decodes 3 barcodes, Dynamsoft decodes all 14 on the same image.

Step 4: Build the Spring Boot Web API

The web app exposes both engines as multipart/form-data POST endpoints so you can test them from a browser via Swagger UI.

Dependencies

Spring Boot 3 requires the new springdoc-openapi-starter-webmvc-ui artifact (the old springdoc-openapi-ui 1.x only works with Spring Boot 2):

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.5</version>
</parent>

<properties>
    <java.version>17</java.version>
    <dynamsoft-dbr.version>11.6.1000</dynamsoft-dbr.version>
    <zxing.version>3.5.3</zxing.version>
    <springdoc-openapi.version>2.8.9</springdoc-openapi.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.dynamsoft</groupId>
        <artifactId>dbr</artifactId>
        <version>${dynamsoft-dbr.version}</version>
    </dependency>
    <dependency>
        <groupId>com.google.zxing</groupId>
        <artifactId>core</artifactId>
        <version>${zxing.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>${springdoc-openapi.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Dynamsoft service

The service reads the uploaded file into a byte[] and passes it to the same capture overload — no temp file needed:

@Service
public class DynamsoftBarcode {

    @Value("${DBR_LICENSE_KEY:LICENSE-KEY}")
    private String license;

    public BarcodeResponse decode(String filename, InputStream is) {
        try {
            LicenseError licenseError = LicenseManager.initLicense(license);
            if (licenseError.getErrorCode() != EnumErrorCode.EC_OK) {
                return BarcodeResponse.builder().filename(filename)
                        .error("License initialization failed: " + licenseError.getErrorString())
                        .build();
            }

            CaptureVisionRouter cvRouter = new CaptureVisionRouter();
            CapturedResult result = cvRouter.capture(is.readAllBytes(),
                    EnumPresetTemplate.PT_READ_BARCODES);

            String[] allResults = null, allFormats = null;
            DecodedBarcodesResult barcodeResult = result.getDecodedBarcodesResult();
            BarcodeResultItem[] items = barcodeResult != null ? barcodeResult.getItems() : null;
            if (items != null) {
                allResults = new String[items.length];
                allFormats = new String[items.length];
                for (int i = 0; i < items.length; ++i) {
                    allResults[i] = items[i].getText();
                    allFormats[i] = items[i].getFormatString();
                }
            }
            return BarcodeResponse.builder()
                    .filename(filename).results(allResults).formats(allFormats).build();
        } catch (Exception ex) {
            return BarcodeResponse.builder().filename(filename).error(ex.getMessage()).build();
        }
    }
}

Controller

Two POST endpoints consume multipart/form-data and produce JSON:

@RestController
public class BarcodeController {

    private final DynamsoftBarcode mDynamsoftBarcode;
    private final ZXingBarcode mZXingBarcode;

    @Autowired
    public BarcodeController(DynamsoftBarcode dynamsoft, ZXingBarcode zxing) {
        mDynamsoftBarcode = dynamsoft;
        mZXingBarcode = zxing;
    }

    @PostMapping(value = "/api/dynamsoft",
                 consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
                 produces = MediaType.APPLICATION_JSON_VALUE)
    public BarcodeResponse getDynamsoft(@RequestPart MultipartFile file) throws Exception {
        return mDynamsoftBarcode.decode(file.getOriginalFilename(), file.getInputStream());
    }

    @PostMapping(value = "/api/zxing",
                 consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
                 produces = MediaType.APPLICATION_JSON_VALUE)
    public BarcodeResponse getZXing(@RequestPart MultipartFile file) throws Exception {
        return mZXingBarcode.decode(file.getOriginalFilename(), file.getInputStream());
    }
}

Build and run

mvn clean package
set DBR_LICENSE_KEY=your-trial-license-key
java -jar target/web-1.0-SNAPSHOT.jar

Then open http://localhost:8080/swagger-ui/index.html, expand POST /api/dynamsoft, click Try it out, upload the test image, and hit Execute:

swagger ui barcode api returning 14 decoded barcodes

The JSON response contains the filename, the decoded text of every barcode, its symbology, and an optional error field — ready to wire into any front end.

Common Developer Questions

What changed between Dynamsoft Barcode Reader 9.x and 11.x for Java?

The legacy BarcodeReader class with decodeFile / decodeBufferedImage was replaced in v11.x by CaptureVisionRouter, which is part of the Dynamsoft Capture Vision framework. Instead of returning TextResult[], you now call cvRouter.capture(...) and walk a DecodedBarcodesResult containing BarcodeResultItem objects.

Which capture overload should I use — file path, byte array, or ImageData?

Use capture(String filePath, String template) when you have a file on disk (CLI, GUI apps), capture(byte[] fileBytes, String template) for uploads and streams (Spring Boot, web services), and capture(ImageData, String) when you already have raw pixel buffers from a camera or video pipeline.

Can I run ZXing and Dynamsoft in the same JVM?

Yes. ZXing is a pure-Java library and Dynamsoft Barcode Reader ships with native JNI libraries — the two coexist without conflicts. The sample projects in this article load both engines and let you compare them on the same image.

Why does Dynamsoft detect more barcodes than ZXing on the same image?

In this sample, ZXing’s GenericMultipleBarcodeReader found 3 of the 14 barcodes. Dynamsoft detected all 14 — including PDF417, Data Matrix, Aztec, GS1 DataBar, and ITF — because its default PT_READ_BARCODES template enables all supported symbologies plus localization and preprocessing modes that ZXing does not provide.

How do I add Swagger UI to a Spring Boot 3 app?

Add the springdoc-openapi-starter-webmvc-ui dependency (2.x) to your pom.xml, restart the app, and open /swagger-ui/index.html. The older springdoc-openapi-ui 1.x only works with Spring Boot 2 and javax.* namespaces.

Where can I get a Dynamsoft Barcode Reader trial license?

You can request a 30-day free trial license from the Dynamsoft Customer Portal and pass it to LicenseManager.initLicense(...) (or set the DBR_LICENSE_KEY environment variable) before creating the CaptureVisionRouter.

Common Issues & Edge Cases

License initialization fails with “Failed to connect to the license server”

Dynamsoft Barcode Reader 11.x requires a one-time online activation when you call LicenseManager.initLicense. If you’re behind a corporate proxy or running in a CI environment, set the HTTPS_PROXY environment variable before starting the JVM, or request an offline trial license from the Customer Portal instead.

UnsatisfiedLinkError: no DynamsoftCore in java.library.path on Linux

The native libraries are bundled inside the JAR and extracted to a temp directory at runtime. On minimal Docker images (e.g. openjdk:17-slim), make sure glibc 2.17+ is installed and /tmp is writable. For Alpine Linux, use the eclipse-temurin:17-jammy base image instead — musl is not supported.

Spring Boot returns 415 Unsupported Media Type on file upload

The endpoints consume multipart/form-data, so the client must send the file with a Content-Type: multipart/form-data header — curl users need -F "file=@..." rather than --data-binary. Swagger UI handles this automatically when you use the file picker.

ZXing returns zero results but Dynamsoft finds barcodes

ZXing’s GenericMultipleBarcodeReader works best with clean, axis-aligned barcodes. It frequently misses PDF417, GS1 DataBar, Aztec, and small Data Matrix codes on dense test sheets. If you must use ZXing for cost reasons, consider pre-processing with OpenCV (rotation, contrast stretch, binarization) — or accept a higher miss rate.

Source Code

Get the complete sample project source code on GitHub