Barcode Signature Java Tutorial - Sign & Verify Documents with GroupDocs
Look, we’ve all been there—you need to track documents through your workflow, verify authenticity, and make sure nothing gets tampered with. Digital signatures sound great in theory, but they can be overkill for internal processes. That’s where barcode signatures come in handy.
Barcode signatures let you embed identifiable information (like employee IDs, transaction numbers, or timestamps) directly into documents. They’re fast to generate, easy to scan, and perfect for workflows where you need quick verification without the complexity of full PKI infrastructure.
In this tutorial, you’ll learn how to use GroupDocs.Signature for Java to add barcode signatures to documents, verify them, search through signed files, and even update or remove signatures when needed. We’ll cover practical use cases, common pitfalls, and performance tips that’ll save you hours of debugging.
What You’ll Master:
- Adding barcode signatures to PDFs, Word docs, and other formats
- Verifying barcode authenticity to prevent tampering
- Searching documents for specific barcode signatures
- Updating existing signatures (like when employee IDs change)
- Removing outdated or invalid signatures
- Choosing the right barcode type for your use case
- Troubleshooting common implementation issues
Let’s get your environment set up first, then we’ll dive into the code.
Prerequisites
Before we start adding barcodes to documents, make sure you’ve got these basics covered:
Required Software:
- Java Development Kit (JDK) 8 or later - GroupDocs.Signature works with Java 8+, but Java 11 or 17 is recommended for better performance
- IDE of your choice - IntelliJ IDEA and Eclipse both work great (this tutorial uses IntelliH IDEA syntax)
- Maven or Gradle - For dependency management (we’ll show both approaches)
Adding GroupDocs.Signature to Your Project
The easiest way to get started is adding the library through your build tool. Here’s how:
Maven Users - Add this to your pom.xml
:
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-signature</artifactId>
<version>23.12</version>
</dependency>
Gradle Users - Add this to your build.gradle
:
implementation 'com.groupdocs:groupdocs-signature:23.12'
Direct JAR Download - If you prefer manual setup, grab the JAR from GroupDocs.Signature releases and add it to your classpath.
Getting Your License Sorted
GroupDocs.Signature isn’t free for production use, but you’ve got options:
- Free Trial - Download from the GroupDocs download page to test features (comes with evaluation watermarks)
- Temporary License - Get 30 days of full access through GroupDocs’ temporary license page (perfect for POCs)
- Full License - For production deployments, check out GroupDocs purchase options
Pro tip: Start with the temporary license for development. The trial version adds watermarks that you don’t want in your testing environment.
Quick Environment Check
Once you’ve added the dependency, do a quick smoke test:
import com.groupdocs.signature.Signature;
public class SignatureTest {
public static void main(String[] args) {
try {
Signature signature = new Signature("test.pdf");
System.out.println("GroupDocs.Signature initialized successfully!");
signature.dispose();
} catch (Exception e) {
System.err.println("Setup issue: " + e.getMessage());
}
}
}
If this runs without errors, you’re good to go. If you hit issues, double-check your dependency version and JDK compatibility.
When to Use Barcode Signatures
Before we jump into code, let’s talk about when barcode signatures actually make sense. They’re not the right tool for every job, but they shine in specific scenarios.
Ideal Use Cases:
- Internal Document Workflows - Track invoices, purchase orders, or internal memos through approval chains
- High-Volume Processing - When you’re signing thousands of documents and need speed over cryptographic security
- Physical-Digital Bridges - Documents that get printed, signed, and scanned back (barcodes survive the print-scan cycle)
- Legacy System Integration - When you need something that barcode scanners can read
- Audit Trails - Embed transaction IDs or timestamps that can be quickly verified later
When to Use Something Else:
- Legal Contracts - Use digital signatures (PKI-based) for legally binding documents
- High-Security Documents - If tampering could cause serious harm, go with cryptographic signatures
- External Partners - Digital signatures are more standardized and trusted
Think of barcode signatures as the middle ground between “no security” and “full cryptographic security.” They’re perfect for internal workflows where you need quick verification but don’t need courtroom-level proof.
Setting Up GroupDocs.Signature for Java
Alright, let’s get into the actual setup. GroupDocs.Signature is designed to be straightforward—you initialize it with a document path, and you’re ready to sign, verify, or search.
Start by importing the core classes you’ll need:
import com.groupdocs.signature.Signature;
Basic Initialization
Here’s the simplest way to get started. Create a Signature
object pointing to your target document:
Signature signature = new Signature("path/to/your/document.pdf");
That’s it for basic setup. The Signature
object now has access to your document and can perform all the operations we’ll cover.
Important Notes:
- The file path can be absolute or relative to your working directory
- GroupDocs supports PDF, Word (DOC/DOCX), Excel, PowerPoint, and image formats
- Make sure your application has read/write permissions for the file locations
- Always call
signature.dispose()
when you’re done to free up resources (or use try-with-resources)
Better practice using try-with-resources:
try (Signature signature = new Signature("path/to/your/document.pdf")) {
// Your signing operations here
} catch (Exception e) {
System.err.println("Error working with document: " + e.getMessage());
}
Now that initialization is clear, let’s start actually signing documents.
Implementation Guide
Sign Document with Barcode Signature
What This Does: Adds a visible barcode signature to your document. The barcode can contain any text you want—employee IDs, transaction numbers, timestamps, whatever makes sense for your workflow.
When You’d Use This: Every time you need to mark a document as processed, approved, or associated with a specific user or transaction. Think of it like stamping a document, but machine-readable.
Step-by-Step Implementation:
1. Define Your File Paths:
Start by setting up where your input document lives and where you want the signed output:
String filePath = "YOUR_DOCUMENT_DIRECTORY/sample.docx";
String outputFilePath = "YOUR_OUTPUT_DIRECTORY/signed_sample.docx";
Pro tip: Use System.getProperty("user.home")
for cross-platform path handling if you’re building something that runs on different machines.
2. Create Your Signature Object:
Initialize the signature handler with your source document:
Signature signature = new Signature(filePath);
3. Configure the Barcode Options:
This is where it gets interesting. You’ll set up exactly how your barcode looks and what data it contains:
BarcodeSignOptions signOptions = new BarcodeSignOptions("John Smith", BarcodeTypes.Code128);
signOptions.setVerticalAlignment(VerticalAlignment.Top);
signOptions.setHorizontalAlignment(HorizontalAlignment.Center);
signOptions.setWidth(100);
signOptions.setHeight(40);
signOptions.setMargin(new java.awt.Rectangle(20, 20, 0, 0));
signOptions.setForeColor(Color.RED);
SignatureFont signatureFont = new SignatureFont();
signatureFont.setSize(12);
signatureFont.setFamilyName("Comic Sans MS");
signOptions.setFont(signatureFont);
Let’s break down what’s happening here:
- “John Smith” - This is the actual data encoded in the barcode (could be an ID, number, whatever)
- BarcodeTypes.Code128 - The barcode format (more on choosing types below)
- Positioning - Top-center placement with 20px margins
- Size - 100x40 pixels (adjust based on your content length)
- Color - Red barcode (use colors that print well if documents get printed)
- Font - For the human-readable text below the barcode
Common Mistake: Setting the barcode too small for the encoded data. If you’re encoding long strings, increase the width or you’ll get scanning errors.
4. Apply the Signature:
Now execute the signing operation:
signature.sign(outputFilePath, signOptions);
This creates a new file with your barcode signature. The original document stays untouched (always a good practice).
Real-World Example:
Here’s how you might use this in an invoice approval system:
String invoiceId = "INV-2025-0042";
String approver = "john.smith@company.com";
String timestamp = LocalDateTime.now().toString();
// Combine into barcode data
String barcodeData = invoiceId + "|" + approver + "|" + timestamp;
BarcodeSignOptions signOptions = new BarcodeSignOptions(barcodeData, BarcodeTypes.QR);
signOptions.setVerticalAlignment(VerticalAlignment.Bottom);
signOptions.setHorizontalAlignment(HorizontalAlignment.Right);
signature.sign(outputFilePath, signOptions);
Now your invoice has a scannable code that contains all approval metadata. Sweet!
Verify Document for Barcode Signature
What This Does: Checks if a document contains a valid barcode signature that matches your criteria. This is how you confirm a document hasn’t been tampered with and contains the expected signature.
When You’d Use This: In approval workflows before processing a document, when receiving signed documents from other systems, or during audits to confirm document authenticity.
Step-by-Step Implementation:
1. Load Your Signed Document:
Point the signature handler at the document you want to verify:
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
2. Set Up Verification Criteria:
Define exactly what you’re looking for. You can verify specific text, barcode types, or locations:
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setAllPages(false); // Only check the first page
verifyOptions.setPageNumber(1);
verifyOptions.setEncodeType(BarcodeTypes.Code128);
verifyOptions.setText("John Smith");
Here’s what each option does:
- setAllPages(false) - Only verify page 1 (faster if you know where the signature is)
- setEncodeType - Must match the type used when signing
- setText - The exact text that should be encoded (case-sensitive!)
3. Run the Verification:
Execute the check and handle the result:
boolean isValid = signature.verify(verifyOptions) != null;
if (isValid) {
System.out.println("Document signature verified successfully!");
} else {
System.out.println("Warning: Signature verification failed!");
}
Practical Verification Patterns:
Sometimes you don’t need exact text matching—you just want to confirm any valid signature exists:
BarcodeVerifyOptions flexibleOptions = new BarcodeVerifyOptions();
flexibleOptions.setAllPages(true);
flexibleOptions.setEncodeType(BarcodeTypes.Code128);
// No setText() call = accept any text with this barcode type
boolean hasAnySignature = signature.verify(flexibleOptions) != null;
Or verify just the format without caring about content:
BarcodeVerifyOptions formatOnly = new BarcodeVerifyOptions();
formatOnly.setEncodeType(BarcodeTypes.QR);
// Confirms a QR code exists, regardless of content
Common Verification Mistake: Forgetting that verification is case-sensitive and whitespace-sensitive. “John Smith” ≠ “john smith” ≠ “John Smith “. Always trim and normalize your data when signing and verifying.
Search Document for Barcode Signature
What This Does: Finds all barcode signatures in a document and returns their details (location, encoded text, barcode type, etc.). Unlike verification which returns true/false, search gives you the actual signature objects.
When You’d Use This: When you need to extract data from signed documents, audit who signed what, or before updating/deleting specific signatures.
Step-by-Step Implementation:
1. Initialize the Search:
Load your document as usual:
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
2. Configure Search Parameters:
Tell GroupDocs where to look and what to look for:
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true); // Search the entire document
You can narrow the search if needed:
BarcodeSearchOptions narrowSearch = new BarcodeSearchOptions();
narrowSearch.setAllPages(false);
narrowSearch.setPageNumber(1); // Only search page 1
narrowSearch.setEncodeType(BarcodeTypes.Code128); // Only find Code128 barcodes
3. Execute the Search:
Run the search and get back a list of found signatures:
java.util.List<BarcodeSignature> signatures = signature.search(BarcodeSignature.class, searchOptions);
System.out.println("Found " + signatures.size() + " barcode signature(s)");
for (BarcodeSignature bcSignature : signatures) {
System.out.println("Barcode Type: " + bcSignature.getEncodeType().getTypeName());
System.out.println("Text: " + bcSignature.getText());
System.out.println("Position: (" + bcSignature.getLeft() + ", " + bcSignature.getTop() + ")");
System.out.println("Size: " + bcSignature.getWidth() + "x" + bcSignature.getHeight());
System.out.println("---");
}
Practical Search Example:
Here’s how you might extract approval data from invoices:
List<BarcodeSignature> foundSignatures = signature.search(BarcodeSignature.class, searchOptions);
for (BarcodeSignature sig : foundSignatures) {
String barcodeData = sig.getText();
// Parse your custom format (remember our invoice example?)
String[] parts = barcodeData.split("\\|");
if (parts.length == 3) {
String invoiceId = parts[0];
String approver = parts[1];
String timestamp = parts[2];
System.out.println("Invoice " + invoiceId + " approved by " + approver + " at " + timestamp);
}
}
Performance Tip: If you’re searching large documents with many signatures, use filters to narrow results. Searching all pages with no filters can be slow on 100+ page documents.
Update Document Barcode Signature
What This Does: Modifies existing barcode signatures in place. You can change the position, size, appearance, or even the encoded data without re-signing the whole document.
When You’d Use This: When employee IDs change, when you need to reposition signatures for formatting reasons, or when updating metadata without invalidating the entire signature.
Important Note: Updating works best when you’ve previously searched for signatures and have their exact references. You can’t update what you can’t find.
Step-by-Step Implementation:
1. Search for Signatures to Update:
First, you need to find the signatures you want to modify:
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true);
List<BarcodeSignature> signatures = signature.search(BarcodeSignature.class, searchOptions);
2. Modify Signature Properties:
Now change whatever properties you need:
List<BarcodeSignature> signaturesToUpdate = new ArrayList<>();
if (!signatures.isEmpty()) {
BarcodeSignature bcSignature = signatures.get(0); // Update the first found signature
// Move it 100px right and 100px down
bcSignature.setLeft(bcSignature.getLeft() + 100);
bcSignature.setTop(bcSignature.getTop() + 100);
// Make it bigger
bcSignature.setWidth(200);
bcSignature.setHeight(50);
signaturesToUpdate.add(bcSignature);
}
You can update multiple properties at once:
bcSignature.setLeft(50);
bcSignature.setTop(50);
bcSignature.setWidth(150);
bcSignature.setHeight(45);
// Note: You can't change the encoded text or barcode type with update
// For that, you'd need to delete and re-sign
3. Save the Updates:
Apply your changes back to the document:
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
signature.update(outputStream, signaturesToUpdate);
// If you want to save to a file instead:
// signature.update("path/to/updated_document.docx", signaturesToUpdate);
Real-World Update Scenario:
Imagine you’re repositioning all signatures to avoid overlap with a new company logo:
List<BarcodeSignature> allSignatures = signature.search(BarcodeSignature.class, searchOptions);
List<BarcodeSignature> toUpdate = new ArrayList<>();
for (BarcodeSignature sig : allSignatures) {
// Move all signatures down by 50px to clear space for new logo
if (sig.getTop() < 100) {
sig.setTop(sig.getTop() + 50);
toUpdate.add(sig);
}
}
if (!toUpdate.isEmpty()) {
signature.update(outputStream, toUpdate);
System.out.println("Repositioned " + toUpdate.size() + " signature(s)");
}
Limitation Alert: You can’t change the barcode type or encoded text with update()
. If you need to change what’s actually encoded, you’ll need to delete the old signature and add a new one.
Delete Document Barcode Signature
What This Does: Removes barcode signatures from a document entirely. Clean removal with no traces left behind.
When You’d Use This: When signatures become invalid, when documents get rejected and need re-approval, or when removing test signatures from production documents.
Step-by-Step Implementation:
1. Find Signatures to Delete:
Just like with updates, you need to search first:
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true);
List<BarcodeSignature> signatures = signature.search(BarcodeSignature.class, searchOptions);
2. Select and Delete:
Choose which signatures to remove and execute the deletion:
List<BarcodeSignature> signaturesToDelete = new ArrayList<>();
// Delete all Code128 signatures (for example)
for (BarcodeSignature sig : signatures) {
if (sig.getEncodeType().equals(BarcodeTypes.Code128)) {
signaturesToDelete.add(sig);
}
}
if (!signaturesToDelete.isEmpty()) {
boolean result = signature.delete(signaturesToDelete);
if (result) {
System.out.println("Successfully deleted " + signaturesToDelete.size() + " signature(s)");
} else {
System.err.println("Failed to delete signatures");
}
}
Selective Deletion Example:
Delete only signatures older than a certain date (assuming you encoded timestamps):
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(30);
List<BarcodeSignature> outdatedSignatures = new ArrayList<>();
for (BarcodeSignature sig : signatures) {
try {
String text = sig.getText();
// Assuming format like "data|timestamp"
String[] parts = text.split("\\|");
if (parts.length > 1) {
LocalDateTime sigDate = LocalDateTime.parse(parts[1]);
if (sigDate.isBefore(cutoffDate)) {
outdatedSignatures.add(sig);
}
}
} catch (Exception e) {
// Skip signatures that don't match our format
continue;
}
}
if (!outdatedSignatures.isEmpty()) {
signature.delete(outdatedSignatures);
System.out.println("Removed " + outdatedSignatures.size() + " outdated signature(s)");
}
Important: Deletion is permanent. There’s no undo. Always work on copies of important documents during development.
Batch Deletion Pattern:
When cleaning up test signatures before going to production:
// Remove all signatures containing "TEST" in their text
List<BarcodeSignature> testSignatures = signatures.stream()
.filter(sig -> sig.getText().toUpperCase().contains("TEST"))
.collect(Collectors.toList());
if (!testSignatures.isEmpty()) {
signature.delete(testSignatures);
System.out.println("Cleaned up " + testSignatures.size() + " test signature(s)");
}
Barcode Types: A Practical Guide
Not all barcodes are created equal. Choosing the right type affects scanning reliability, data capacity, and printer compatibility. Here’s what you need to know:
Code128 (Most Common Choice)
When to use it:
- You’re encoding alphanumeric data (letters and numbers)
- You need high data density
- Documents will be printed and scanned
Pros:
- Compact for the amount of data
- Works with standard barcode scanners
- Prints clearly even at smaller sizes
Cons:
- Limited to ASCII characters
- Not as error-resistant as 2D codes
Example:
BarcodeSignOptions code128 = new BarcodeSignOptions("INV-2025-042", BarcodeTypes.Code128);
QR Code (Best for Mobile)
When to use it:
- Users might scan with smartphones
- You need to encode lots of data (URLs, JSON, etc.)
- Documents might get damaged or partially obscured
Pros:
- Huge data capacity (up to 4,000 characters)
- Built-in error correction
- Works with phone cameras
Cons:
- Takes up more space
- Needs higher resolution for small sizes
Example:
String jsonData = "{\"invoice\":\"INV-042\",\"amount\":1500.00,\"approver\":\"john@company.com\"}";
BarcodeSignOptions qrCode = new BarcodeSignOptions(jsonData, BarcodeTypes.QR);
Code39 (Old Reliable)
When to use it:
- Working with legacy systems
- Need human-readable embedded text
- Government or military applications
Pros:
- Widely supported by old scanners
- Simple and predictable
- No checksum required
Cons:
- Low data density
- Limited character set
- Takes up lots of space
Data Matrix (Compact Powerhouse)
When to use it:
- Space is extremely limited
- Marking small items
- High-density data storage needed
Pros:
- Very compact
- Good error correction
- Works on curved surfaces
Cons:
- Requires higher-quality printing
- Less common scanner support
Quick Comparison:
Barcode Type | Data Capacity | Best For | Size |
---|---|---|---|
Code128 | ~100 chars | General purpose | Small |
QR Code | ~4,000 chars | Mobile scanning | Medium |
Code39 | ~43 chars | Legacy systems | Large |
Data Matrix | ~3,000 chars | Tiny spaces | Very small |
My Recommendation: Start with Code128 for basic ID tracking. Switch to QR if you need more data or mobile scanning. Only use Code39 if you’re integrating with legacy systems.
Common Issues & Solutions
Problem: “Barcode Not Found During Verification”
Symptoms: Your verification returns false even though you can see the barcode in the document.
Common Causes:
- Case mismatch - You signed with “John Smith” but verifying “john smith”
- Whitespace - Extra spaces in the text
- Wrong barcode type - Signed with QR, verifying for Code128
- Wrong page number - Looking at page 1 when signature is on page 2
Solution:
// Always normalize your data
String signatureText = originalText.trim().toLowerCase();
// When signing:
BarcodeSignOptions signOptions = new BarcodeSignOptions(signatureText, BarcodeTypes.Code128);
// When verifying:
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setText(signatureText); // Use the same normalized text
verifyOptions.setEncodeType(BarcodeTypes.Code128);
Problem: “Barcode Appears Blurry or Unreadable”
Symptoms: Printed documents have barcodes that won’t scan.
Causes:
- Barcode too small for the encoded data
- Low-resolution printing
- Poor color contrast
Solution:
// Increase size for longer strings
int dataLength = barcodeText.length();
int minWidth = Math.max(100, dataLength * 10); // 10px per character minimum
BarcodeSignOptions signOptions = new BarcodeSignOptions(barcodeText, BarcodeTypes.Code128);
signOptions.setWidth(minWidth);
signOptions.setHeight(60); // Taller = easier to scan
// Use high-contrast colors
signOptions.setForeColor(Color.BLACK); // Black on white is most reliable
signOptions.setBackColor(Color.WHITE);
Problem: “OutOfMemoryError with Large Documents”
Symptoms: Application crashes when processing multi-hundred-page PDFs.
Cause: Loading entire document into memory.
Solution:
// Process pages in batches instead of all at once
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(false);
List<BarcodeSignature> allSignatures = new ArrayList<>();
for (int page = 1; page <= totalPages; page++) {
searchOptions.setPageNumber(page);
List<BarcodeSignature> pageSignatures = signature.search(BarcodeSignature.class, searchOptions);
allSignatures.addAll(pageSignatures);
// Process and clear as you go
if (page % 10 == 0) {
System.gc(); // Suggest garbage collection every 10 pages
}
}
Problem: “Signature Position Inconsistent Across Document Types”
Symptoms: Your positioning code works for PDFs but signatures appear wrong in Word docs.
Cause: Different formats use different coordinate systems.
Solution:
// Use relative positioning instead of absolute
signOptions.setVerticalAlignment(VerticalAlignment.Bottom);
signOptions.setHorizontalAlignment(HorizontalAlignment.Right);
// Then use margins for fine-tuning
Padding margin = new Padding();
margin.setRight(50); // 50px from right edge
margin.setBottom(50); // 50px from bottom
signOptions.setMargin(margin);
// This works consistently across PDF, DOCX, XLSX, etc.
Problem: “Updated Signatures Lose Formatting”
Symptoms: After updating position, barcode looks different.
Cause: Not all properties carry over during update.
Solution:
// When updating, explicitly set all visual properties
bcSignature.setLeft(newLeft);
bcSignature.setTop(newTop);
bcSignature.setWidth(originalWidth); // Don't forget these!
bcSignature.setHeight(originalHeight);
// Note: Color and font can't be updated - you'd need to delete and re-sign
Best Practices for Production
Performance Optimization
1. Reuse Signature Objects
Don’t create new Signature
instances for every operation. Reuse them when working with the same document:
// Bad - Creates new instance each time
public void processDocument(String path) {
Signature sig1 = new Signature(path);
sig1.search(/* ... */);
Signature sig2 = new Signature(path); // Unnecessary reload
sig2.verify(/* ... */);
}
// Good - Reuse the same instance
public void processDocument(String path) {
try (Signature signature = new Signature(path)) {
signature.search(/* ... */);
signature.verify(/* ... */);
signature.update(/* ... */);
}
}
2. Search Specific Pages Only
If you know where signatures live, don’t scan the entire document:
// Slow for 100+ page documents
BarcodeSearchOptions slowSearch = new BarcodeSearchOptions();
slowSearch.setAllPages(true);
// Fast - only check where signatures actually are
BarcodeSearchOptions fastSearch = new BarcodeSearchOptions();
fastSearch.setAllPages(false);
fastSearch.setPageNumber(1); // Only check cover page
3. Use Appropriate Barcode Types
Data-dense barcodes take longer to generate. Use the simplest type that meets your needs:
// Overkill for simple IDs
BarcodeSignOptions slow = new BarcodeSignOptions("12345", BarcodeTypes.QR);
// Faster and more appropriate
BarcodeSignOptions fast = new BarcodeSignOptions("12345", BarcodeTypes.Code128);
Security Considerations
1. Don’t Store Sensitive Data in Barcodes
Remember: barcodes are visible and scannable by anyone. Don’t encode passwords, SSNs, or other PII:
// Bad - exposes sensitive data
String barcodeData = "SSN:123-45-6789|Password:hunter2";
// Good - use reference IDs
String barcodeData = "USER-REF-7721X"; // Look up sensitive data server-side
2. Implement Server-Side Verification
Never trust client-side barcode verification alone:
// Client scans barcode and extracts "APPROVED-BY-ADMIN"
// Always verify server-side:
public boolean verifyApproval(String documentId, String scannedData) {
// Load document from secure storage
Signature signature = new Signature(getSecureDocument(documentId));
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setText(scannedData);
boolean isValid = signature.verify(verifyOptions) != null;
// Also check against your database
boolean isInDatabase = checkApprovalDatabase(documentId, scannedData);
return isValid && isInDatabase;
}
3. Add Timestamps to Prevent Replay Attacks
Include timestamps in your barcode data to prevent old signatures from being reused:
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
String timestamp = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS).toString();
String barcodeData = "APPROVAL|" + userId + "|" + timestamp;
// When verifying, check the timestamp isn't too old
public boolean isSignatureRecent(String barcodeData, int maxAgeHours) {
String[] parts = barcodeData.split("\\|");
if (parts.length < 3) return false;
LocalDateTime signatureTime = LocalDateTime.parse(parts[2]);
LocalDateTime now = LocalDateTime.now();
long hoursSince = ChronoUnit.HOURS.between(signatureTime, now);
return hoursSince <= maxAgeHours;
}
Error Handling Patterns
1. Always Use Try-With-Resources
Prevent memory leaks by ensuring Signature objects get disposed:
try (Signature signature = new Signature(documentPath)) {
// Your operations here
signature.sign(outputPath, signOptions);
} catch (Exception e) {
logger.error("Failed to sign document: " + documentPath, e);
throw new DocumentSigningException("Could not process document", e);
}
2. Validate Before Operating
Check documents exist and are accessible before trying to sign them:
public void signDocument(String inputPath, String outputPath) {
File inputFile = new File(inputPath);
if (!inputFile.exists()) {
throw new IllegalArgumentException("Input file not found: " + inputPath);
}
if (!inputFile.canRead()) {
throw new SecurityException("Cannot read input file: " + inputPath);
}
File outputDir = new File(outputPath).getParentFile();
if (outputDir != null && !outputDir.exists()) {
outputDir.mkdirs();
}
try (Signature signature = new Signature(inputPath)) {
signature.sign(outputPath, signOptions);
}
}
3. Handle Concurrent Access
If multiple threads might access the same document:
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ConcurrentHashMap;
public class SignatureManager {
private final ConcurrentHashMap<String, ReentrantLock> documentLocks = new ConcurrentHashMap<>();
public void signDocument(String documentPath, BarcodeSignOptions options) {
ReentrantLock lock = documentLocks.computeIfAbsent(documentPath, k -> new ReentrantLock());
lock.lock();
try (Signature signature = new Signature(documentPath)) {
signature.sign(documentPath + ".signed", options);
} finally {
lock.unlock();
}
}
}
Testing Your Implementation
1. Unit Test Template
Here’s a solid starting point for testing your barcode operations:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.*;
public class BarcodeSignatureTest {
@TempDir
Path tempDir;
@Test
public void testSignAndVerify() throws Exception {
// Setup
String testDoc = "test-document.pdf";
String outputDoc = tempDir.resolve("signed-test.pdf").toString();
String testData = "TEST-USER-12345";
// Sign
try (Signature signature = new Signature(testDoc)) {
BarcodeSignOptions signOptions = new BarcodeSignOptions(testData, BarcodeTypes.Code128);
signature.sign(outputDoc, signOptions);
}
// Verify
try (Signature signature = new Signature(outputDoc)) {
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setText(testData);
verifyOptions.setEncodeType(BarcodeTypes.Code128);
boolean isValid = signature.verify(verifyOptions) != null;
assertTrue(isValid, "Signature should be valid");
}
}
@Test
public void testSearchFindsSignature() throws Exception {
String signedDoc = "signed-document.pdf";
try (Signature signature = new Signature(signedDoc)) {
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true);
List<BarcodeSignature> signatures = signature.search(BarcodeSignature.class, searchOptions);
assertFalse(signatures.isEmpty(), "Should find at least one signature");
assertEquals("TEST-USER-12345", signatures.get(0).getText());
}
}
}
2. Integration Testing Checklist
- Test with all document formats you’ll support (PDF, DOCX, XLSX)
- Verify signatures survive print-and-scan cycles (if applicable)
- Test with maximum-length barcode data
- Verify signature positioning on various page sizes
- Test concurrent signing of multiple documents
- Validate memory usage with large documents
- Confirm barcode scannability with actual hardware scanners
Logging and Monitoring
1. Add Meaningful Logs
Help future-you debug issues:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DocumentSigningService {
private static final Logger logger = LoggerFactory.getLogger(DocumentSigningService.class);
public void signDocument(String docPath, String barcodeData) {
logger.info("Starting signature process for document: {}", docPath);
logger.debug("Barcode data length: {} characters", barcodeData.length());
try (Signature signature = new Signature(docPath)) {
BarcodeSignOptions options = new BarcodeSignOptions(barcodeData, BarcodeTypes.Code128);
long startTime = System.currentTimeMillis();
signature.sign(docPath + ".signed", options);
long duration = System.currentTimeMillis() - startTime;
logger.info("Successfully signed document in {}ms", duration);
} catch (Exception e) {
logger.error("Failed to sign document: {}", docPath, e);
throw new RuntimeException("Signature operation failed", e);
}
}
}
2. Track Key Metrics
Monitor these to catch issues early:
// Track success/failure rates
metricsService.incrementCounter("signature.sign.success");
metricsService.incrementCounter("signature.verify.failure");
// Track performance
metricsService.recordTimer("signature.sign.duration", duration);
// Track document sizes
metricsService.recordHistogram("signature.document.pages", pageCount);
Pro Tips from Real-World Usage
Tip 1: Batch Processing Strategy
When signing hundreds of documents, process in batches with progress tracking:
public void signBatch(List<String> documents, String barcodePrefix) {
int total = documents.size();
int completed = 0;
int failed = 0;
for (String docPath : documents) {
try {
String barcodeData = barcodePrefix + "-" + UUID.randomUUID().toString();
signDocument(docPath, barcodeData);
completed++;
if (completed % 10 == 0) {
logger.info("Progress: {}/{} documents signed", completed, total);
}
} catch (Exception e) {
failed++;
logger.warn("Failed to sign: {}", docPath, e);
}
}
logger.info("Batch complete: {} succeeded, {} failed", completed, failed);
}
Tip 2: Configuration Management
Externalize your barcode settings for easy tuning:
public class BarcodeConfig {
private int defaultWidth = 100;
private int defaultHeight = 40;
private String defaultBarcodeType = "Code128";
private String defaultColor = "BLACK";
// Load from properties file or environment variables
public BarcodeConfig() {
this.defaultWidth = Integer.parseInt(
System.getProperty("barcode.width", "100")
);
// ... load other properties
}
public BarcodeSignOptions createDefaultOptions(String text) {
BarcodeSignOptions options = new BarcodeSignOptions(text, getBarcodeType());
options.setWidth(defaultWidth);
options.setHeight(defaultHeight);
options.setForeColor(getColor());
return options;
}
}
Tip 3: Custom Barcode Data Format
Create a standardized format for your barcode data:
public class BarcodeDataBuilder {
private String userId;
private String action;
private LocalDateTime timestamp;
private String documentId;
public BarcodeDataBuilder setUserId(String userId) {
this.userId = userId;
return this;
}
public BarcodeDataBuilder setAction(String action) {
this.action = action;
return this;
}
public BarcodeDataBuilder setDocumentId(String documentId) {
this.documentId = documentId;
return this;
}
public String build() {
this.timestamp = LocalDateTime.now();
// Format: VERSION|USER|ACTION|DOC_ID|TIMESTAMP|CHECKSUM
String data = String.join("|",
"v1",
userId,
action,
documentId,
timestamp.toString()
);
// Add simple checksum
int checksum = data.hashCode();
return data + "|" + checksum;
}
public static ParsedBarcodeData parse(String barcodeData) {
String[] parts = barcodeData.split("\\|");
if (parts.length != 6 || !parts[0].equals("v1")) {
throw new IllegalArgumentException("Invalid barcode format");
}
return new ParsedBarcodeData(parts[1], parts[2], parts[3],
LocalDateTime.parse(parts[4]));
}
}
// Usage:
String barcodeData = new BarcodeDataBuilder()
.setUserId("john.smith")
.setAction("APPROVED")
.setDocumentId("INV-2025-042")
.build();
Tip 4: Document Format Detection
Automatically adjust settings based on document type:
public BarcodeSignOptions getOptimalOptions(String documentPath, String text) {
String extension = documentPath.substring(documentPath.lastIndexOf(".") + 1).toLowerCase();
BarcodeSignOptions options = new BarcodeSignOptions(text, BarcodeTypes.Code128);
switch (extension) {
case "pdf":
// PDFs can handle larger, detailed barcodes
options.setWidth(150);
options.setHeight(50);
break;
case "docx":
case "doc":
// Word docs need compact signatures that don't disrupt flow
options.setWidth(100);
options.setHeight(35);
options.setVerticalAlignment(VerticalAlignment.Bottom);
break;
case "xlsx":
case "xls":
// Excel needs signatures that don't obscure data
options.setWidth(80);
options.setHeight(30);
options.setHorizontalAlignment(HorizontalAlignment.Right);
break;
default:
// Safe defaults
options.setWidth(100);
options.setHeight(40);
}
return options;
}
Next Steps
Ready to implement? Start with a simple sign-and-verify proof of concept. Once that’s working, gradually add search, update, and delete functionality as you need them.
Need more advanced features? Check out the GroupDocs.Signature documentation for:
- QR code signatures with custom data
- Digital signatures for legal documents
- Image and stamp signatures
- Multi-signature workflows
Still have questions? The GroupDocs support forum is active and helpful. You can also explore the API reference for detailed method documentation.