Scanning Made Simple: Barcode Reader with ZXing in Java

ZXing (Zebra Crossing) is a powerful open-source barcode image processing library that enables Java applications to read and generate various 1D and 2D barcode formats. This comprehensive guide covers implementation, optimization, and real-world usage scenarios.


ZXing Library Overview

Supported Barcode Formats:

  • 1D Barcodes: UPC-A, UPC-E, EAN-8, EAN-13, Code 39, Code 93, Code 128
  • 2D Barcodes: QR Code, Data Matrix, PDF417, Aztec
  • Industrial: ITF, RSS-14, RSS-Expanded

Project Setup and Dependencies

1. Maven Dependencies

<!-- pom.xml -->
<dependencies>
<!-- Core ZXing library -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.2</version>
</dependency>
<!-- ZXing JavaSE extension for image processing -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.2</version>
</dependency>
<!-- Image processing utilities -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<version>3.10.1</version>
</dependency>
</dependencies>

2. Basic Barcode Reader Implementation

package com.barcode.reader;
import com.google.zxing.*;
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.File;
import java.io.IOException;
import java.util.*;
public class BasicBarcodeReader {
/**
* Read barcode from image file
*/
public static BarcodeResult readBarcode(File imageFile) throws IOException, NotFoundException {
BufferedImage image = ImageIO.read(imageFile);
return readBarcode(image);
}
/**
* Read barcode from BufferedImage
*/
public static BarcodeResult readBarcode(BufferedImage image) throws NotFoundException {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
// Configure barcode formats to try
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, Arrays.asList(
BarcodeFormat.QR_CODE,
BarcodeFormat.CODE_128,
BarcodeFormat.CODE_39,
BarcodeFormat.EAN_13,
BarcodeFormat.EAN_8,
BarcodeFormat.UPC_A,
BarcodeFormat.DATA_MATRIX
));
Result result = new MultiFormatReader().decode(bitmap, hints);
return new BarcodeResult(
result.getText(),
result.getBarcodeFormat(),
result.getResultPoints()
);
}
/**
* Result container class
*/
public static class BarcodeResult {
private final String text;
private final BarcodeFormat format;
private final ResultPoint[] points;
public BarcodeResult(String text, BarcodeFormat format, ResultPoint[] points) {
this.text = text;
this.format = format;
this.points = points != null ? points.clone() : new ResultPoint[0];
}
// Getters
public String getText() { return text; }
public BarcodeFormat getFormat() { return format; }
public ResultPoint[] getPoints() { return points.clone(); }
@Override
public String toString() {
return String.format("Format: %s, Text: %s", format, text);
}
}
}

Advanced Barcode Reading Features

1. Multi-Barcode Detection

public class AdvancedBarcodeReader {
private final MultiFormatReader reader;
private final Map<DecodeHintType, Object> hints;
public AdvancedBarcodeReader() {
this.reader = new MultiFormatReader();
this.hints = createDefaultHints();
}
private Map<DecodeHintType, Object> createDefaultHints() {
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
// Enable multiple barcode detection
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.ALSO_INVERTED, Boolean.TRUE);
// Specify supported formats
hints.put(DecodeHintType.POSSIBLE_FORMATS, Arrays.asList(
BarcodeFormat.UPC_A, BarcodeFormat.UPC_E,
BarcodeFormat.EAN_8, BarcodeFormat.EAN_13,
BarcodeFormat.CODE_39, BarcodeFormat.CODE_93, BarcodeFormat.CODE_128,
BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX,
BarcodeFormat.PDF_417, BarcodeFormat.AZTEC
));
return hints;
}
/**
* Detect multiple barcodes in a single image
*/
public List<BarcodeResult> readMultipleBarcodes(BufferedImage image) {
List<BarcodeResult> results = new ArrayList<>();
try {
// First attempt - standard reading
results.addAll(readBarcodesFromImage(image));
// Second attempt - try inverted image
BufferedImage inverted = invertImage(image);
results.addAll(readBarcodesFromImage(inverted));
// Remove duplicates
return removeDuplicateResults(results);
} catch (NotFoundException e) {
// No barcodes found
return Collections.emptyList();
}
}
private List<BarcodeResult> readBarcodesFromImage(BufferedImage image) throws NotFoundException {
List<BarcodeResult> results = new ArrayList<>();
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
// Use MultipleBarcodeReader if available
try {
Result[] rawResults = reader.decodeMultiple(bitmap, hints);
if (rawResults != null) {
for (Result result : rawResults) {
results.add(new BarcodeResult(
result.getText(),
result.getBarcodeFormat(),
result.getResultPoints()
));
}
}
} catch (NotFoundException e) {
// Try single barcode reading
try {
Result singleResult = reader.decode(bitmap, hints);
results.add(new BarcodeResult(
singleResult.getText(),
singleResult.getBarcodeFormat(),
singleResult.getResultPoints()
));
} catch (NotFoundException e2) {
// No barcodes found in this attempt
}
}
return results;
}
private BufferedImage invertImage(BufferedImage original) {
BufferedImage inverted = new BufferedImage(
original.getWidth(), original.getHeight(), original.getType());
for (int x = 0; x < original.getWidth(); x++) {
for (int y = 0; y < original.getHeight(); y++) {
int rgb = original.getRGB(x, y);
inverted.setRGB(x, y, ~rgb);
}
}
return inverted;
}
private List<BarcodeResult> removeDuplicateResults(List<BarcodeResult> results) {
Set<String> seenTexts = new HashSet<>();
List<BarcodeResult> uniqueResults = new ArrayList<>();
for (BarcodeResult result : results) {
if (seenTexts.add(result.getText())) {
uniqueResults.add(result);
}
}
return uniqueResults;
}
}

2. Image Preprocessing for Better Detection

public class ImagePreprocessor {
/**
* Enhance image for better barcode detection
*/
public static BufferedImage preprocessImage(BufferedImage original) {
BufferedImage processed = original;
// Convert to grayscale if necessary
if (original.getType() != BufferedImage.TYPE_BYTE_GRAY) {
processed = convertToGrayscale(original);
}
// Apply contrast enhancement
processed = enhanceContrast(processed);
// Apply noise reduction
processed = reduceNoise(processed);
return processed;
}
private static BufferedImage convertToGrayscale(BufferedImage colorImage) {
BufferedImage grayImage = new BufferedImage(
colorImage.getWidth(), colorImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
for (int x = 0; x < colorImage.getWidth(); x++) {
for (int y = 0; y < colorImage.getHeight(); y++) {
int rgb = colorImage.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
int gray = (int) (0.299 * r + 0.587 * g + 0.114 * b);
int grayRGB = (gray << 16) | (gray << 8) | gray;
grayImage.setRGB(x, y, grayRGB);
}
}
return grayImage;
}
private static BufferedImage enhanceContrast(BufferedImage image) {
// Simple contrast enhancement using histogram stretching
int width = image.getWidth();
int height = image.getHeight();
BufferedImage enhanced = new BufferedImage(width, height, image.getType());
// Find min and max pixel values
int min = 255, max = 0;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int gray = image.getRGB(x, y) & 0xFF;
if (gray < min) min = gray;
if (gray > max) max = gray;
}
}
// Apply contrast stretch
double scale = 255.0 / (max - min);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int gray = image.getRGB(x, y) & 0xFF;
int enhancedGray = (int) ((gray - min) * scale);
enhancedGray = Math.max(0, Math.min(255, enhancedGray));
int enhancedRGB = (enhancedGray << 16) | (enhancedGray << 8) | enhancedGray;
enhanced.setRGB(x, y, enhancedRGB);
}
}
return enhanced;
}
private static BufferedImage reduceNoise(BufferedImage image) {
// Simple median filter for noise reduction
int width = image.getWidth();
int height = image.getHeight();
BufferedImage filtered = new BufferedImage(width, height, image.getType());
int[][] kernel = {
{1, 1, 1},
{1, 1, 1},
{1, 1, 1}
};
for (int x = 1; x < width - 1; x++) {
for (int y = 1; y < height - 1; y++) {
List<Integer> neighbors = new ArrayList<>();
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
int gray = image.getRGB(x + dx, y + dy) & 0xFF;
neighbors.add(gray);
}
}
// Get median value
Collections.sort(neighbors);
int median = neighbors.get(4); // Middle value for 3x3 kernel
int medianRGB = (median << 16) | (median << 8) | median;
filtered.setRGB(x, y, medianRGB);
}
}
return filtered;
}
}

Real-Time Camera Barcode Scanning

1. Webcam Integration with Webcam-Capture

<!-- Additional dependency for webcam capture -->
<dependency>
<groupId>com.github.sarxos</groupId>
<artifactId>webcam-capture</artifactId>
<version>0.3.12</version>
</dependency>

2. Real-Time Barcode Scanner

package com.barcode.reader.camera;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.WebcamResolution;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class CameraBarcodeScanner {
private Webcam webcam;
private WebcamPanel webcamPanel;
private JFrame frame;
private ScheduledExecutorService executor;
private BarcodeListener listener;
private volatile boolean scanning = false;
public CameraBarcodeScanner(BarcodeListener listener) {
this.listener = listener;
initializeCamera();
setupUI();
}
private void initializeCamera() {
// Get default webcam with reasonable resolution
webcam = Webcam.getDefault();
if (webcam != null) {
webcam.setViewSize(WebcamResolution.VGA.getSize());
} else {
throw new IllegalStateException("No webcam found");
}
}
private void setupUI() {
frame = new JFrame("Barcode Scanner");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
webcamPanel = new WebcamPanel(webcam);
webcamPanel.setFPSDisplayed(true);
webcamPanel.setDisplayDebugInfo(true);
webcamPanel.setImageSizeDisplayed(true);
webcamPanel.setMirrored(true);
JButton startButton = new JButton("Start Scanning");
startButton.addActionListener(e -> startScanning());
JButton stopButton = new JButton("Stop Scanning");
stopButton.addActionListener(e -> stopScanning());
JPanel controlPanel = new JPanel();
controlPanel.add(startButton);
controlPanel.add(stopButton);
frame.add(webcamPanel, BorderLayout.CENTER);
frame.add(controlPanel, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
public void startScanning() {
if (scanning) return;
scanning = true;
webcam.open();
executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(this::captureAndProcess, 0, 100, TimeUnit.MILLISECONDS);
listener.onScanningStarted();
}
public void stopScanning() {
scanning = false;
if (executor != null) {
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (webcam.isOpen()) {
webcam.close();
}
listener.onScanningStopped();
}
private void captureAndProcess() {
if (!scanning || !webcam.isOpen()) return;
BufferedImage image = webcam.getImage();
if (image == null) return;
try {
List<BarcodeResult> results = processImage(image);
if (!results.isEmpty()) {
SwingUtilities.invokeLater(() -> {
for (BarcodeResult result : results) {
listener.onBarcodeDetected(result);
}
});
}
} catch (Exception e) {
// Log error but don't stop scanning
System.err.println("Error processing frame: " + e.getMessage());
}
}
private List<BarcodeResult> processImage(BufferedImage image) {
List<BarcodeResult> results = new ArrayList<>();
try {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, Arrays.asList(
BarcodeFormat.QR_CODE, BarcodeFormat.CODE_128, 
BarcodeFormat.EAN_13, BarcodeFormat.UPC_A
));
Result result = new MultiFormatReader().decode(bitmap, hints);
results.add(new BarcodeResult(
result.getText(),
result.getBarcodeFormat(),
result.getResultPoints()
));
} catch (NotFoundException e) {
// No barcode found in this frame - normal during scanning
} catch (Exception e) {
System.err.println("Unexpected error during barcode processing: " + e.getMessage());
}
return results;
}
public interface BarcodeListener {
void onBarcodeDetected(BarcodeResult result);
void onScanningStarted();
void onScanningStopped();
}
public static class BarcodeResult {
private final String text;
private final BarcodeFormat format;
private final ResultPoint[] points;
public BarcodeResult(String text, BarcodeFormat format, ResultPoint[] points) {
this.text = text;
this.format = format;
this.points = points != null ? points.clone() : new ResultPoint[0];
}
public String getText() { return text; }
public BarcodeFormat getFormat() { return format; }
public ResultPoint[] getPoints() { return points.clone(); }
@Override
public String toString() {
return String.format("[%s] %s", format, text);
}
}
}

Enterprise-Grade Barcode Scanner

1. Production-Ready Scanner Service

package com.barcode.reader.service;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.common.GlobalHistogramBinarizer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class BarcodeScannerService {
private final ExecutorService processingPool;
private final Map<DecodeHintType, Object> decodeHints;
private final AtomicInteger successfulScans;
private final AtomicInteger failedScans;
private volatile boolean shutdown = false;
public BarcodeScannerService(int threadPoolSize) {
this.processingPool = Executors.newFixedThreadPool(threadPoolSize);
this.decodeHints = createOptimizedHints();
this.successfulScans = new AtomicInteger(0);
this.failedScans = new AtomicInteger(0);
}
private Map<DecodeHintType, Object> createOptimizedHints() {
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
// Performance and accuracy hints
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.ALSO_INVERTED, Boolean.TRUE);
hints.put(DecodeHintType.PURE_BARCODE, Boolean.FALSE);
// Character set hint for better text decoding
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
// Supported formats based on use case
hints.put(DecodeHintType.POSSIBLE_FORMATS, Arrays.asList(
BarcodeFormat.UPC_A, BarcodeFormat.UPC_E,
BarcodeFormat.EAN_8, BarcodeFormat.EAN_13,
BarcodeFormat.CODE_39, BarcodeFormat.CODE_93, BarcodeFormat.CODE_128,
BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX,
BarcodeFormat.PDF_417, BarcodeFormat.AZTEC,
BarcodeFormat.ITF, BarcodeFormat.RSS_14
));
return hints;
}
/**
* Process a batch of images asynchronously
*/
public CompletableFuture<List<ScanResult>> processBatch(List<File> imageFiles) {
List<CompletableFuture<ScanResult>> futures = new ArrayList<>();
for (File imageFile : imageFiles) {
CompletableFuture<ScanResult> future = CompletableFuture.supplyAsync(() -> {
try {
return processSingleImage(imageFile);
} catch (Exception e) {
return new ScanResult(imageFile.getName(), null, e.getMessage());
}
}, processingPool);
futures.add(future);
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}
/**
* Monitor directory for new images and process automatically
*/
public void startDirectoryWatcher(Path directory, ScanResultListener listener) throws IOException {
WatchService watchService = FileSystems.getDefault().newWatchService();
directory.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
Thread watcherThread = new Thread(() -> {
while (!shutdown) {
try {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
Path newFile = directory.resolve((Path) event.context());
if (isImageFile(newFile)) {
processFileAsync(newFile.toFile(), listener);
}
}
}
key.reset();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
System.err.println("Error in directory watcher: " + e.getMessage());
}
}
});
watcherThread.setDaemon(true);
watcherThread.start();
}
private void processFileAsync(File imageFile, ScanResultListener listener) {
CompletableFuture.supplyAsync(() -> processSingleImage(imageFile), processingPool)
.thenAccept(listener::onScanResult);
}
private ScanResult processSingleImage(File imageFile) {
try {
BufferedImage image = ImageIO.read(imageFile);
if (image == null) {
failedScans.incrementAndGet();
return new ScanResult(imageFile.getName(), null, "Invalid image format");
}
// Try different binarization strategies
List<Result> results = tryMultipleBinarizationStrategies(image);
if (results.isEmpty()) {
failedScans.incrementAndGet();
return new ScanResult(imageFile.getName(), null, "No barcode detected");
}
successfulScans.incrementAndGet();
return new ScanResult(imageFile.getName(), results, null);
} catch (IOException e) {
failedScans.incrementAndGet();
return new ScanResult(imageFile.getName(), null, "IO Error: " + e.getMessage());
} catch (Exception e) {
failedScans.incrementAndGet();
return new ScanResult(imageFile.getName(), null, "Processing Error: " + e.getMessage());
}
}
private List<Result> tryMultipleBinarizationStrategies(BufferedImage image) {
List<Result> results = new ArrayList<>();
LuminanceSource source = new BufferedImageLuminanceSource(image);
// Strategy 1: HybridBinarizer (default)
try {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Result result = new MultiFormatReader().decode(bitmap, decodeHints);
if (result != null) results.add(result);
} catch (NotFoundException e) {
// Continue to next strategy
}
// Strategy 2: GlobalHistogramBinarizer
try {
BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
Result result = new MultiFormatReader().decode(bitmap, decodeHints);
if (result != null) results.add(result);
} catch (NotFoundException e) {
// Continue to next strategy
}
// Strategy 3: Try with image preprocessing
try {
BufferedImage processed = ImagePreprocessor.preprocessImage(image);
LuminanceSource processedSource = new BufferedImageLuminanceSource(processed);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(processedSource));
Result result = new MultiFormatReader().decode(bitmap, decodeHints);
if (result != null) results.add(result);
} catch (NotFoundException e) {
// All strategies failed
}
return results;
}
private boolean isImageFile(Path file) {
String fileName = file.getFileName().toString().toLowerCase();
return fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") ||
fileName.endsWith(".png") || fileName.endsWith(".bmp") ||
fileName.endsWith(".gif");
}
public ScannerStatistics getStatistics() {
return new ScannerStatistics(
successfulScans.get(),
failedScans.get(),
processingPool instanceof ThreadPoolExecutor ? 
((ThreadPoolExecutor) processingPool).getActiveCount() : 0
);
}
public void shutdown() {
shutdown = true;
processingPool.shutdown();
try {
if (!processingPool.awaitTermination(5, TimeUnit.SECONDS)) {
processingPool.shutdownNow();
}
} catch (InterruptedException e) {
processingPool.shutdownNow();
Thread.currentThread().interrupt();
}
}
public interface ScanResultListener {
void onScanResult(ScanResult result);
}
public static class ScanResult {
private final String filename;
private final List<Result> results;
private final String error;
public ScanResult(String filename, List<Result> results, String error) {
this.filename = filename;
this.results = results != null ? new ArrayList<>(results) : Collections.emptyList();
this.error = error;
}
public boolean isSuccess() { return error == null && !results.isEmpty(); }
public String getFilename() { return filename; }
public List<Result> getResults() { return Collections.unmodifiableList(results); }
public String getError() { return error; }
}
public static class ScannerStatistics {
private final int successfulScans;
private final int failedScans;
private final int activeThreads;
public ScannerStatistics(int successfulScans, int failedScans, int activeThreads) {
this.successfulScans = successfulScans;
this.failedScans = failedScans;
this.activeThreads = activeThreads;
}
// Getters
public int getSuccessfulScans() { return successfulScans; }
public int getFailedScans() { return failedScans; }
public int getActiveThreads() { return activeThreads; }
public double getSuccessRate() { 
return successfulScans + failedScans > 0 ? 
(double) successfulScans / (successfulScans + failedScans) : 0.0; 
}
}
}

Integration Examples

1. Spring Boot REST API

@RestController
@RequestMapping("/api/barcode")
public class BarcodeScannerController {
private final BarcodeScannerService scannerService;
public BarcodeScannerController() {
this.scannerService = new BarcodeScannerService(4);
}
@PostMapping("/scan")
public ResponseEntity<ScanResponse> scanBarcode(@RequestParam("file") MultipartFile file) {
try {
// Convert MultipartFile to temporary file
File tempFile = File.createTempFile("barcode_", ".tmp");
file.transferTo(tempFile);
BarcodeScannerService.ScanResult result = scannerService.processSingleImage(tempFile);
// Clean up temp file
tempFile.delete();
if (result.isSuccess()) {
List<BarcodeData> barcodes = result.getResults().stream()
.map(this::mapToBarcodeData)
.collect(Collectors.toList());
return ResponseEntity.ok(new ScanResponse(true, barcodes, null));
} else {
return ResponseEntity.badRequest()
.body(new ScanResponse(false, null, result.getError()));
}
} catch (Exception e) {
return ResponseEntity.internalServerError()
.body(new ScanResponse(false, null, "Server error: " + e.getMessage()));
}
}
@GetMapping("/statistics")
public ResponseEntity<ScannerStatistics> getStatistics() {
return ResponseEntity.ok(scannerService.getStatistics());
}
private BarcodeData mapToBarcodeData(Result result) {
return new BarcodeData(
result.getText(),
result.getBarcodeFormat().name(),
Arrays.stream(result.getResultPoints())
.map(p -> new Point(p.getX(), p.getY()))
.collect(Collectors.toList())
);
}
// DTO classes
public static class ScanResponse {
private final boolean success;
private final List<BarcodeData> barcodes;
private final String error;
public ScanResponse(boolean success, List<BarcodeData> barcodes, String error) {
this.success = success;
this.barcodes = barcodes;
this.error = error;
}
// Getters and setters for JSON serialization
public boolean isSuccess() { return success; }
public List<BarcodeData> getBarcodes() { return barcodes; }
public String getError() { return error; }
}
public static class BarcodeData {
private final String text;
private final String format;
private final List<Point> points;
public BarcodeData(String text, String format, List<Point> points) {
this.text = text;
this.format = format;
this.points = points;
}
// Getters
public String getText() { return text; }
public String getFormat() { return format; }
public List<Point> getPoints() { return points; }
}
public static class Point {
private final float x;
private final float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
// Getters
public float getX() { return x; }
public float getY() { return y; }
}
}

2. Desktop Application with JavaFX

public class BarcodeScannerApp extends Application {
private BarcodeScannerService scannerService;
private TableView<ScanResult> resultsTable;
private Label statusLabel;
@Override
public void start(Stage primaryStage) {
scannerService = new BarcodeScannerService(2);
setupUI(primaryStage);
setupDirectoryWatcher();
}
private void setupUI(Stage stage) {
stage.setTitle("Barcode Scanner");
// Create results table
resultsTable = new TableView<>();
TableColumn<ScanResult, String> fileColumn = new TableColumn<>("File");
fileColumn.setCellValueFactory(new PropertyValueFactory<>("filename"));
TableColumn<ScanResult, String> resultColumn = new TableColumn<>("Result");
resultColumn.setCellValueFactory(cell -> {
ScanResult result = cell.getValue();
if (result.isSuccess()) {
String barcodes = result.getResults().stream()
.map(r -> String.format("[%s] %s", r.getBarcodeFormat(), r.getText()))
.collect(Collectors.joining(", "));
return new SimpleStringProperty(barcodes);
} else {
return new SimpleStringProperty("Error: " + result.getError());
}
});
resultsTable.getColumns().addAll(fileColumn, resultColumn);
// Create controls
Button selectFolderBtn = new Button("Select Watch Folder");
selectFolderBtn.setOnAction(e -> selectWatchFolder());
statusLabel = new Label("Ready");
VBox layout = new VBox(10, 
new HBox(10, selectFolderBtn, statusLabel),
resultsTable
);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout, 800, 600));
stage.show();
}
private void selectWatchFolder() {
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle("Select Folder to Watch for Images");
File selectedFolder = chooser.showDialog(resultsTable.getScene().getWindow());
if (selectedFolder != null) {
try {
scannerService.startDirectoryWatcher(
selectedFolder.toPath(), 
this::handleScanResult
);
statusLabel.setText("Watching: " + selectedFolder.getName());
} catch (IOException e) {
showError("Failed to start directory watcher: " + e.getMessage());
}
}
}
private void handleScanResult(BarcodeScannerService.ScanResult result) {
Platform.runLater(() -> {
resultsTable.getItems().add(0, result); // Add to top
if (result.isSuccess()) {
// Show success notification
showNotification("Barcode detected in " + result.getFilename());
}
// Update statistics
BarcodeScannerService.ScannerStatistics stats = scannerService.getStatistics();
statusLabel.setText(String.format(
"Scanned: %d successful, %d failed (%.1f%%)",
stats.getSuccessfulScans(),
stats.getFailedScans(),
stats.getSuccessRate() * 100
));
});
}
private void setupDirectoryWatcher() {
// Auto-start watching a default directory if exists
Path defaultDir = Paths.get(System.getProperty("user.home"), "BarcodeScans");
if (Files.exists(defaultDir) && Files.isDirectory(defaultDir)) {
try {
scannerService.startDirectoryWatcher(defaultDir, this::handleScanResult);
statusLabel.setText("Watching: " + defaultDir.getFileName());
} catch (IOException e) {
System.err.println("Failed to watch default directory: " + e.getMessage());
}
}
}
private void showNotification(String message) {
// Implement system notification or toast
System.out.println("NOTIFICATION: " + message);
}
private void showError(String message) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Error");
alert.setContentText(message);
alert.showAndWait();
}
@Override
public void stop() {
if (scannerService != null) {
scannerService.shutdown();
}
}
public static void main(String[] args) {
launch(args);
}
}

Performance Optimization and Best Practices

1. Memory Management

public class MemoryEfficientScanner {
private static final int MAX_IMAGE_SIZE = 1920 * 1080; // Full HD
public Result scanLargeImageEfficiently(File imageFile) throws IOException {
BufferedImage image = readAndResizeImage(imageFile);
return scanImage(image);
}
private BufferedImage readAndResizeImage(File imageFile) throws IOException {
// Read image metadata first to avoid loading huge images into memory
ImageInputStream stream = ImageIO.createImageInputStream(imageFile);
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if (!readers.hasNext()) {
throw new IOException("No suitable image reader found");
}
ImageReader reader = readers.next();
reader.setInput(stream);
int width = reader.getWidth(0);
int height = reader.getHeight(0);
// Calculate scaling factor if image is too large
double scale = 1.0;
if (width * height > MAX_IMAGE_SIZE) {
scale = Math.sqrt((double) MAX_IMAGE_SIZE / (width * height));
}
// Read scaled image
ImageReadParam param = reader.getDefaultReadParam();
if (scale < 1.0) {
int scaledWidth = (int) (width * scale);
int scaledHeight = (int) (height * scale);
param.setSourceSubsampling(
width / scaledWidth, 
height / scaledHeight, 
0, 0
);
}
BufferedImage image = reader.read(0, param);
reader.dispose();
stream.close();
return image;
}
}

2. Configuration Management

public class BarcodeScannerConfig {
private final Properties properties;
public BarcodeScannerConfig() {
this.properties = loadProperties();
}
private Properties loadProperties() {
Properties props = new Properties();
try (InputStream input = getClass().getResourceAsStream("/barcode-scanner.properties")) {
if (input != null) {
props.load(input);
}
} catch (IOException e) {
// Use defaults
}
// Set defaults
props.setProperty("scanner.threads", "4");
props.setProperty("scanner.timeout", "5000");
props.setProperty("scanner.maxImageSize", "2097152"); // 2MB
props.setProperty("scanner.supportedFormats", "QR_CODE,CODE_128,EAN_13");
return props;
}
public Map<DecodeHintType, Object> getDecodeHints() {
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.TRY_HARDER, 
Boolean.parseBoolean(properties.getProperty("scanner.tryHarder", "true")));
String formats = properties.getProperty("scanner.supportedFormats");
if (formats != null) {
List<BarcodeFormat> formatList = Arrays.stream(formats.split(","))
.map(String::trim)
.map(BarcodeFormat::valueOf)
.collect(Collectors.toList());
hints.put(DecodeHintType.POSSIBLE_FORMATS, formatList);
}
return hints;
}
public int getThreadPoolSize() {
return Integer.parseInt(properties.getProperty("scanner.threads", "4"));
}
public long getProcessingTimeout() {
return Long.parseLong(properties.getProperty("scanner.timeout", "5000"));
}
}

Conclusion

ZXing provides a robust foundation for barcode reading in Java applications:

Key Strengths:

  • Wide format support - 1D and 2D barcodes
  • High accuracy with multiple decoding strategies
  • Performance optimized for various use cases
  • Active community and regular updates

Best Practices:

  • Use appropriate binarization strategies for different image qualities
  • Implement image preprocessing for challenging conditions
  • Use thread pools for batch processing
  • Monitor performance and success rates
  • Provide fallback strategies for difficult images

Use Cases:

  • Retail - Point of sale systems
  • Logistics - Package tracking and inventory
  • Healthcare - Patient and medication tracking
  • Manufacturing - Quality control and part tracking
  • Mobile applications - Product information lookup

The combination of ZXing's powerful decoding capabilities with proper Java integration patterns enables the creation of enterprise-grade barcode scanning solutions that are both reliable and maintainable.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper