Add Barcode PDF Java – Sign & Verify with GroupDocs
If you need a fast, visual way to tag documents for internal workflows, adding a barcode to a PDF in Java is a perfect solution. With GroupDocs.Signature, you can embed, verify, search, update, and delete barcode signatures without the overhead of full PKI‑based digital signatures. This tutorial walks you through every step, from environment setup to production‑ready best practices.
Quick Answers
- What library adds barcode to PDFs in Java? GroupDocs.Signature for Java.
- Can I sign PDF, Word, and images? Yes – the API supports 30+ formats.
- Do I need a license for development? A temporary 30‑day license is free; a full license is required for production.
- How many lines of code to sign a PDF? Just two lines: create a
Signatureobject and callsign. - Is barcode verification case‑sensitive? Yes – the text must match exactly, including case and whitespace.
What is add barcode pdf java?
add barcode pdf java refers to the process of using Java code to embed a barcode (such as Code128, QR, or Data Matrix) into a PDF file. This technique provides a machine‑readable tag that can be scanned or programmatically verified, enabling fast document tracking in internal systems.
Why use barcode signatures instead of full digital signatures?
Barcode signatures are 30‑50 % faster to generate and verify than PKI‑based digital signatures, and they work reliably after a print‑scan cycle. They also require no certificate management, making them ideal for high‑volume, internal workflows where cryptographic proof isn’t mandatory.
Prerequisites
- Java Development Kit (JDK) 8+ – Java 11 or 17 is recommended for better performance.
- IDE – IntelliJ IDEA or Eclipse (the examples use IntelliJ syntax).
- Build tool – Maven or Gradle for dependency management.
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 have flexible options:
- Free Trial – Download from the GroupDocs download page to test features (evaluation watermarks are added).
- Temporary License – Get 30 days of full access through GroupDocs’ temporary license page – perfect for proofs of concept.
- Full License – For production deployments, check out GroupDocs purchase options.
Pro tip: Start with the temporary license for development; it removes watermarks once you switch to a permanent key.
Quick Environment Check
Once you’ve added the dependency, run a simple 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 the program runs without errors, your environment is ready. If you encounter issues, verify the JDK version and the exact library version you added.
When to Use Barcode Signatures
Barcode signatures shine in specific scenarios:
- Internal Document Workflows – Track invoices, purchase orders, or memos through approval chains.
- High‑Volume Processing – Sign thousands of documents quickly; barcode generation is up to 2× faster than full digital signing.
- Print‑Scan Bridges – Barcodes survive the print‑scan cycle, making them ideal for hybrid paper‑digital processes.
- Legacy System Integration – Existing barcode scanners can read the tags without additional software.
- Audit Trails – Embed transaction IDs or timestamps that auditors can verify instantly.
Avoid barcode signatures for legal contracts, high‑security documents, or external partner exchanges where PKI‑based digital signatures are required.
Setting Up GroupDocs.Signature for Java
Core Class Definition
The Signature class is the entry point for all signing, verification, search, update, and delete operations in GroupDocs.Signature.
import com.groupdocs.signature.Signature;
Basic Initialization
Create a Signature object that points to the target file:
Signature signature = new Signature("path/to/your/document.pdf");
Important notes
- Paths can be absolute or relative; use
System.getProperty("user.home")for cross‑platform compatibility. - GroupDocs supports 30+ input and output formats, including PDF, DOCX, XLSX, PPTX, and PNG.
- Always release resources with
signature.dispose()or a try‑with‑resources block:
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 you’re ready to add barcodes.
How do I add a barcode to a PDF in Java?
Load the source PDF with new Signature("input.pdf"), configure a BarcodeSignature object (e.g., Code128 with the text “John Smith”), and call sign to produce the signed file – all in three concise lines of code. This approach lets you embed a machine‑readable tag while keeping the original document layout intact, and it works for any supported format, not just PDFs.
Step‑by‑Step Implementation
1. Define File Paths
Set the locations for the source and output files:
String filePath = "YOUR_DOCUMENT_DIRECTORY/sample.docx";
String outputFilePath = "YOUR_OUTPUT_DIRECTORY/signed_sample.docx";
2. Create the Signature Handler
Initialize the handler with the source document:
Signature signature = new Signature(filePath);
3. Configure Barcode Options
A BarcodeSignature object defines the visual and data properties of the barcode to be embedded. Set the barcode type, encoded text, position, size, color, and optional human‑readable font:
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);
Pro tip: If the encoded string exceeds 20 characters, increase the barcode width to avoid scanning errors.
4. Apply the Signature
Execute the signing operation and generate the output file:
signature.sign(outputFilePath, signOptions);
5. Real‑World Example
In an invoice‑approval system you might embed the approver’s employee ID and a timestamp:
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);
The resulting PDF now carries a scannable barcode that encodes all necessary approval metadata.
How can I verify a barcode signature in a Java document?
The Signature.verify method checks a document for matching barcode signatures based on provided options, returning a boolean that indicates whether the expected barcode is present. Verification is useful for automated workflows where you need to confirm that a document has been processed by the correct party before further actions.
Verification Steps
1. Load the Signed Document
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
2. Set Verification Criteria
Define the exact text, barcode format, and page number you expect:
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setAllPages(false); // Only check the first page
verifyOptions.setPageNumber(1);
verifyOptions.setEncodeType(BarcodeTypes.Code128);
verifyOptions.setText("John Smith");
3. Run 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!");
}
Common pattern: To simply confirm that any barcode of a given type exists, omit the setText filter:
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 only the barcode format without caring about the content:
BarcodeVerifyOptions formatOnly = new BarcodeVerifyOptions();
formatOnly.setEncodeType(BarcodeTypes.QR);
// Confirms a QR code exists, regardless of content
Warning: Verification is case‑ and whitespace‑sensitive; always trim and normalize data before signing.
How do I search for barcode signatures inside a document?
The Signature.search method scans a document for barcode signatures that match the supplied BarcodeSearchOptions, returning a collection that includes each barcode’s location, page number, and encoded value. This capability enables bulk extraction of metadata without opening each file manually.
Search Workflow
1. Initialize Search
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
2. Configure Search Parameters
Specify page range, barcode type, or text filters:
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true); // Search the entire document
Optionally narrow the search to improve performance:
BarcodeSearchOptions narrowSearch = new BarcodeSearchOptions();
narrowSearch.setAllPages(false);
narrowSearch.setPageNumber(1); // Only search page 1
narrowSearch.setEncodeType(BarcodeTypes.Code128); // Only find Code128 barcodes
3. Execute Search
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("---");
}
4. Extract Data Example
Pull approval data from every barcode on an invoice batch:
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: For documents over 100 pages, always limit the search to the pages that actually contain barcodes; this can cut runtime by up to 70 %.
How can I update an existing barcode signature?
The Signature.update method modifies visual attributes of an existing barcode signature—such as position, size, or color—while preserving the original encoded data. This is handy when layout changes are required after the document has already been signed.
Update Procedure
1. Find Signatures to Update
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 Desired Properties
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 change several attributes 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 Changes
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);
4. Real‑World Scenario
Reposition all signatures to avoid overlapping a newly added 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: Changing the encoded text requires deletion and a fresh signature insertion.
How do I delete barcode signatures from a document?
The Signature.delete method permanently removes selected barcode signatures from a document, erasing both the visual element and its encoded data. Use this operation when cleaning up test signatures or when a barcode is no longer relevant.
Deletion Steps
1. Locate Signatures
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. Delete Selected Signatures
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");
}
}
3. Conditional Deletion Example
Remove only barcodes older than a specific timestamp (assuming the timestamp is part of the encoded text):
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)");
}
Caution: Deletion cannot be undone; always operate on a copy of production files during testing.
4. Batch Deletion Pattern
Clean up test signatures before a release:
// 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
Choosing the right barcode impacts scanning reliability, data capacity, and printer compatibility.
Code128 (Most Common Choice)
- When to use: Alphanumeric data, high density, printed documents.
- Pros: Compact, works with standard scanners, prints clearly at small sizes.
- Cons: Limited to ASCII, less error‑resistant than 2‑D codes.
Example:
BarcodeSignOptions code128 = new BarcodeSignOptions("INV-2025-042", BarcodeTypes.Code128);
QR Code (Best for Mobile)
- When to use: Mobile scanning, large data payloads (URLs, JSON, etc.), documents that may get damaged.
- Pros: Up to 4 000 characters, built‑in error correction, smartphone‑friendly.
- Cons: Larger visual footprint, requires 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: Legacy scanner environments, need for human‑readable text.
- Pros: Wide legacy support, simple format, no checksum required.
- Cons: Low data density, limited character set, occupies more space.
Data Matrix (Compact Powerhouse)
- When to use: Extremely limited space, marking tiny items, high‑density data.
- Pros: Very compact, strong error correction, works on curved surfaces.
- Cons: Requires high‑quality printing, less common scanner support.
Quick Comparison
| Barcode Type | Data Capacity | Best For | Typical Size |
|---|---|---|---|
| Code128 | ~100 chars | General‑purpose tagging | Small |
| QR Code | ~4 000 chars | Mobile scanning, rich data | Medium |
| Code39 | ~43 chars | Legacy hardware | Large |
| Data Matrix | ~3 000 chars | Tiny spaces, industrial tags | Very small |
Recommendation: Start with Code128 for simple IDs. Switch to QR when you need to embed URLs or larger payloads. Use Code39 only for legacy integrations.
Common Issues & Solutions
Problem: “Barcode Not Found During Verification”
Symptoms: Verification returns false even though the barcode is visible.
Typical causes
- Case mismatch (e.g., “John Smith” vs. “john smith”).
- Extra whitespace in the encoded text.
- Wrong barcode type specified in the verification options.
- Searching the wrong page number.
Solution: Normalize the text before signing and verification, and ensure the setEncodeType matches the original type.
// 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 barcodes cannot be scanned.
Causes
- Barcode dimensions too small for the encoded data.
- Low‑resolution printer settings.
- Insufficient contrast between barcode color and background.
Solution: Increase barcode width/height, use high‑contrast colors (black on white), and set printer DPI to at least 300 dpi.
// 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 PDFs with hundreds of pages.
Cause: The library loads the entire document into memory.
Solution: Enable streaming mode and process pages incrementally.
// 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: Barcodes appear in different locations when signing PDFs vs. Word documents.
Cause: PDF uses a bottom‑left origin, while Word uses a top‑left origin.
Solution: Detect the document type and apply the appropriate coordinate transformation.
// 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 calling update, the barcode’s color or font changes unexpectedly.
Cause: Not all visual properties are persisted during an update operation.
Solution: Re‑apply any visual settings after the update call.
// 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
- Reuse Signature Instances – Create a single
Signatureobject per document and reuse it for multiple operations.
// 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(/* ... */);
}
}
- Search Specific Pages Only – Limit the page range to where barcodes are expected.
// 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
- Choose the Simplest Barcode Type – Simpler barcodes generate faster; only use QR or Data Matrix when you truly need the extra capacity.
// Overkill for simple IDs
BarcodeSignOptions slow = new BarcodeSignOptions("12345", BarcodeTypes.QR);
// Faster and more appropriate
BarcodeSignOptions fast = new BarcodeSignOptions("12345", BarcodeTypes.Code128);
Security Considerations
- Never Encode Sensitive Personal Data – Barcodes are easily readable; avoid PII such as SSNs or passwords.
// 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
- Validate Server‑Side – Never trust client‑side verification alone; always re‑verify on a trusted backend.
// 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;
}
- Add Timestamps – Include a timestamp in the barcode payload to prevent replay attacks.
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
- Always Use Try‑With‑Resources to ensure
Signatureobjects are 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);
}
- Validate File Access before attempting to sign.
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);
}
}
- Synchronize Access if multiple threads may work on the same file.
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
Unit Test Template
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());
}
}
}
Integration Checklist
- ✅ Test all supported formats (PDF, DOCX, XLSX, PPTX, PNG).
- ✅ Verify barcodes survive a print‑scan cycle.
- ✅ Stress‑test with maximum‑length data strings.
- ✅ Confirm positioning on A4, Letter, and custom page sizes.
- ✅ Run concurrent signing on multiple documents.
- ✅ Monitor memory usage for documents > 500 pages.
- ✅ Ensure barcode scanners read the output reliably.
Logging and Monitoring
Add meaningful logs around each operation:
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);
}
}
}
Track key metrics such as processing time, memory consumption, and error rates:
// 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
Batch Processing Strategy
When handling hundreds of files, process them in batches and report progress:
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);
}
Externalize Configuration
Store barcode settings (type, size, color) in a properties file for easy tuning without recompiling:
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;
}
}
Standardize Barcode Payload
Use a delimited format like EMPID|TIMESTAMP|DOCID to make parsing simple and consistent:
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();
Detect Document Type Automatically
Adjust barcode dimensions based on whether the target is PDF, Word, or an image:
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;
}
Additional Resources
- GroupDocs.Signature documentation – Full API guide and usage examples.
- GroupDocs support forum – Community help and troubleshooting.
- API reference – Detailed method signatures and parameter descriptions.
Frequently Asked Questions
Q: Can I use GroupDocs.Signature with Java 17?
A: Yes – the library is fully compatible with JDK 8, 11, and 17.
Q: Does the barcode survive a print‑scan cycle?
A: When you use Code128 or QR with sufficient size and contrast, the barcode remains scannable after printing and scanning.
Q: How many barcodes can a single document contain?
A: There is no hard limit; however, for optimal performance keep the total count below 200 per document.
Q: Is a license required for development builds?
A: A temporary license removes evaluation watermarks; a full license is mandatory for any production deployment.
Q: Can I sign password‑protected PDFs?
A: Yes – provide the password when creating the Signature object; the API will unlock the file internally.
Last Updated: 2026-07-15
Tested With: GroupDocs.Signature 23.9 for Java
Author: GroupDocs