Java Document Signing Tutorial - Add Text Signatures with Event Handling
Introduction
Need to sign documents programmatically in your Java application? Whether you’re building a contract management system, an automated approval workflow, or just need to add signatures to PDFs without manual intervention, you’re in the right place.
Here’s the challenge: signing documents in code isn’t as simple as clicking a button. You need to track the process, handle errors gracefully, and know exactly when operations complete—especially when dealing with large documents or batch processing. That’s where event-driven document signing comes in.
In this tutorial, you’ll learn how to implement robust document signing in Java using GroupDocs.Signature. We’ll focus on text signatures (perfect for approval stamps, custom labels, or initial placeholders) and show you how to monitor every step of the signing process through event handling.
What you’ll build:
- A document signing system with real-time progress tracking
- Event handlers that notify you when operations start, progress, and complete
- Text signature implementation with customizable positioning
- Production-ready code you can adapt for your specific needs
By the end, you’ll have a working implementation that you can drop into your existing Java applications. Let’s dive in!
Why Event Handling Matters for Document Signing
Before we get to the code, let’s talk about why event handling is crucial (and not just a nice-to-have feature).
When you’re signing documents programmatically, things can go wrong. Files might be locked, operations might take longer than expected, or you might need to process hundreds of documents in a queue. Without event handling, you’re flying blind—you don’t know if operations have started, how long they’ll take, or when they’re actually done.
Real-world scenarios where event handling saves the day:
- Batch processing: Sign 500 employee contracts overnight and get notified if any fail
- User feedback: Show a progress bar when users upload documents for signing
- Logging and auditing: Track exactly when each document was signed for compliance
- Error recovery: Detect failures mid-process and retry or alert administrators
- Performance monitoring: Identify slow operations and optimize your workflow
Think of it like tracking a package delivery. You don’t just want to know “it’ll arrive eventually”—you want real-time updates on where it is and when it’ll get there. Same principle applies here.
Prerequisites
Before implementing text signing with event handling, let’s make sure you’ve got everything you need.
Required Libraries and Dependencies
To use GroupDocs.Signature for Java, you’ll need to add it to your project. Here’s how, depending on your build tool:
Maven:
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-signature</artifactId>
<version>23.12</version>
</dependency>
Gradle:
implementation 'com.groupdocs:groupdocs-signature:23.12'
Alternatively, you can download the JAR directly from GroupDocs.Signature for Java releases and add it manually to your project’s classpath.
Pro tip: If you’re working on an enterprise project, consider using a dependency management tool (Maven or Gradle) rather than manual JAR management. It makes version updates and dependency resolution much easier down the line.
Environment Setup
Make sure your development environment meets these requirements:
- JDK 8 or higher (JDK 11 or 17 recommended for production)
- A compatible IDE like IntelliJ IDEA, Eclipse, or NetBeans
- Maven or Gradle installed if using those tools (check with
mvn -v
orgradle -v
)
Knowledge Prerequisites
You’ll get the most out of this tutorial if you have:
- Basic understanding of Java programming (classes, methods, exception handling)
- Familiarity with event-driven architecture (the concept of listeners and callbacks)
- Experience working with file I/O in Java (we’ll be reading and writing documents)
Don’t worry if you’re not an expert in these areas—we’ll explain concepts as we go. The code examples are designed to be self-explanatory.
Setting Up GroupDocs.Signature for Java
Now that you’ve got the prerequisites covered, let’s get GroupDocs.Signature initialized in your project.
Installation and License
Step 1: Add the dependency to your project’s build file (shown above in Prerequisites).
Step 2: Obtain a license. You have several options:
- Free trial: Get a temporary license from GroupDocs for testing (usually 30 days)
- Purchase: Buy a full license from GroupDocs.com for production use
- Evaluation mode: Use the library without a license (adds watermarks to output documents)
For development and testing, the free trial is your best bet. Just apply the license in your code before creating a Signature
object.
Basic Initialization
Here’s how you initialize GroupDocs.Signature in your Java application:
import com.groupdocs.signature.Signature;
public class SignatureSetup {
public static void main(String[] args) {
String filePath = "path/to/your/document.pdf";
Signature signature = new Signature(filePath);
// Your document is now ready to be signed with GroupDocs.Signature for Java.
}
}
What’s happening here:
- We’re creating a
Signature
object by passing in the path to our document - This object represents the document we want to sign—think of it as opening a file in editing mode
- The library supports PDFs, Word documents, Excel files, images, and more (check the docs for the full list)
Common mistake to avoid: Make sure the file path is correct and the file exists. If the file isn’t found, you’ll get a FileNotFoundException
. Always validate file paths in production code, especially if they come from user input.
Implementation Guide
Alright, time to build the actual signing system. We’ll implement this step by step, starting with event handlers and then tying everything together in the signing operation.
Sign Process Start Event
When your application kicks off a signing operation, you probably want to know about it immediately. Maybe you need to log the start time, update a progress indicator, or just record that the operation began. That’s what the start event gives you.
Overview
The SignStarted
event fires the moment a signing operation begins—before any actual processing happens. This is perfect for initialization tasks, logging, or updating your UI to show that something’s in progress.
Steps
Step 1: Define the Event Handler
Create a method that’ll be called when the signing process starts:
import com.groupdocs.signature.handler.events.ProcessStartEventArgs;
import com.groupdocs.signature.handler.events.ProcessStartEventHandler;
public class SignProcessStart {
public static void onSignStarted(Signature sender, ProcessStartEventArgs args) {
System.out.println("Signing process started: " + args.getSignatureDefinition().getSignatureType());
}
}
What this does:
- The
onSignStarted
method receives two parameters: theSignature
object that triggered the event andProcessStartEventArgs
containing details about the operation args.getSignatureDefinition().getSignatureType()
tells you what type of signature is being applied (text, image, digital, etc.)- In this example, we’re just printing to console, but you could log to a file, update a database, or trigger other actions
Real-world application: In a document management system, you might use this event to update a document’s status to “Signing In Progress” in your database, so other parts of your application know not to modify the file.
Step 2: Subscribe to the Event
Now hook up your event handler to the Signature
object:
signature.SignStarted.add(new ProcessStartEventHandler() {
public void invoke(Signature sender, ProcessStartEventArgs args) {
SignProcessStart.onSignStarted(sender, args);
}
});
This tells GroupDocs.Signature, “Hey, whenever a signing operation starts, call my onSignStarted
method.”
Pro tip: You can subscribe multiple handlers to the same event if you need different parts of your application to respond. They’ll all get called in the order they were added.
Sign Progress Event
For large documents or batch operations, you don’t want users staring at a frozen screen wondering if your app crashed. Progress events let you provide real-time feedback.
Overview
The SignProgress
event fires periodically during the signing process, giving you updates on how much work has been completed. This is especially useful for:
- Showing progress bars in your UI
- Logging performance metrics
- Detecting operations that are taking too long (potential issues)
Steps
Step 1: Define the Progress Event Handler
Set up a method to capture and handle progress updates:
import com.groupdocs.signature.handler.events.ProcessProgressEventArgs;
import com.groupdocs.signature.handler.events.ProcessProgressEventHandler;
public class SignProgress {
public static void onSignProgress(Signature sender, ProcessProgressEventArgs args) {
System.out.println("Signing progress: " + args.getPercentCompleted() + "% completed");
}
}
What you’re seeing:
args.getPercentCompleted()
gives you a number from 0 to 100 representing progress- This event fires multiple times during a signing operation (frequency depends on document complexity)
- You can use this percentage to update a progress bar:
progressBar.setValue(args.getPercentCompleted())
Performance consideration: Progress events fire frequently, so keep your handler lightweight. Don’t do expensive operations (like database writes) on every progress update—consider batching or only updating at certain intervals (e.g., every 10%).
Step 2: Subscribe to the Progress Event
Add your progress handler to the event pipeline:
signature.SignProgress.add(new ProcessProgressEventHandler() {
public void invoke(Signature sender, ProcessProgressEventArgs args) {
SignProgress.onSignProgress(sender, args);
}
});
Pro tip for UI applications: If you’re updating a UI from this event, make sure to do it on the correct thread. For JavaFX, use Platform.runLater()
. For Swing, use SwingUtilities.invokeLater()
.
Sign Completion Event
Once the signing process finishes (successfully or not), you need to know about it so you can take appropriate action—save the file, notify users, clean up resources, or handle errors.
Overview
The SignCompleted
event fires when a signing operation finishes. Unlike the start and progress events, this one gives you the final result—success or failure—along with details about what was accomplished.
Steps
Step 1: Define the Completion Event Handler
Create a method to handle completion notifications:
import com.groupdocs.signature.handler.events.ProcessCompleteEventArgs;
import com.groupdocs.signature.handler.events.ProcessCompleteEventHandler;
public class SignCompletion {
public static void onSignCompleted(Signature sender, ProcessCompleteEventArgs args) {
System.out.println("Signing completed: " + args.getSignatureDefinition().getSignatureType());
}
}
What’s happening:
- This method gets called after the signing operation finishes
args
contains information about what was signed and whether it succeeded- You can check
args.getSucceeded()
to determine if the operation completed successfully
Real-world scenarios:
- Success case: Move the signed document to a “completed” folder, send confirmation email, update database status
- Failure case: Log the error, notify administrators, move document to a “failed” queue for manual review
- Audit logging: Record who signed what and when for compliance purposes
Step 2: Subscribe to the Completion Event
Hook up your completion handler:
signature.SignCompleted.add(new ProcessCompleteEventHandler() {
public void invoke(Signature sender, ProcessCompleteEventArgs args) {
SignCompletion.onSignCompleted(sender, args);
}
});
Important note: Always check for errors in your completion handler. Just because the event fired doesn’t mean everything went perfectly. Inspect the args
object for error details.
Text Signature Signing
Now we bring it all together—setting up event handlers AND actually signing a document with a text signature. This is where the magic happens.
Overview
Text signatures are versatile. Use them for:
- Approval stamps: “APPROVED”, “REVIEWED”, “FINAL”
- Initials: Quick marking of documents without full signatures
- Custom labels: Department names, reference numbers, dates
- Placeholders: Mark where physical signatures should go on printed documents
Unlike digital signatures (which provide cryptographic verification), text signatures are visual markers. They’re perfect when you need a visible indicator but don’t require legal-grade verification.
Steps
Step 1: Configure and Sign the Document
Here’s the complete implementation that ties everything together:
import com.groupdocs.signature.options.sign.TextSignOptions;
import java.io.File;
import java.nio.file.Paths;
public class SignWithTextSignature {
public static void signDocument() throws Exception {
String filePath = "YOUR_DOCUMENT_DIRECTORY";
String fileName = Paths.get(filePath).getFileName().toString();
String outputFilePath = new File("YOUR_OUTPUT_DIRECTORY", "SignWithTextEvents/" + fileName).getPath();
Signature signature = new Signature(filePath);
// Subscribe to signing events
signature.SignStarted.add(new ProcessStartEventHandler() {
public void invoke(Signature sender, ProcessStartEventArgs args) {
SignProcessStart.onSignStarted(sender, args);
}
});
signature.SignProgress.add(new ProcessProgressEventHandler() {
public void invoke(Signature sender, ProcessProgressEventArgs args) {
SignProgress.onSignProgress(sender, args);
}
});
signature.SignCompleted.add(new ProcessCompleteEventHandler() {
public void invoke(Signature sender, ProcessCompleteEventArgs args) {
SignCompletion.onSignCompleted(sender, args);
}
});
// Define text signature options
TextSignOptions options = new TextSignOptions("John Smith");
options.setLeft(100); // Set the left position of the signature
options.setTop(100); // Set the top position of the signature
// Perform signing operation
signature.sign(outputFilePath, options);
}
}
Breaking down the code:
File paths: We’re loading a document from
filePath
and saving the signed version tooutputFilePath
. Always use separate output paths—never overwrite your original documents!Event subscriptions: All three event handlers are attached before we start signing. This ensures we don’t miss any events.
TextSignOptions: This object defines what the signature looks like and where it goes:
"John Smith"
is the text that’ll appear in the signaturesetLeft(100)
andsetTop(100)
position the signature 100 pixels from the left and top edges- You can also customize font, color, size, rotation, and more (check the API docs)
The
sign()
call: This method does the actual signing. It processes the document, applies the signature, and saves the result—firing events along the way.
Positioning tips:
- Coordinates are in pixels, starting from the top-left corner
- For PDFs, each page might have different dimensions—test on representative documents
- Consider document margins when positioning (don’t place signatures too close to edges)
- You can use negative values for
setTop()
to position relative to the bottom of the page
Common mistake to avoid: Forgetting to create the output directory. If YOUR_OUTPUT_DIRECTORY
doesn’t exist, the operation will fail. Always ensure output directories exist before signing:
File outputDir = new File("YOUR_OUTPUT_DIRECTORY", "SignWithTextEvents");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
Common Issues & Solutions
Even with perfect code, things can go wrong. Here are the most common issues you’ll encounter when implementing document signing, along with how to fix them.
Issue 1: FileNotFoundException or Access Denied
Symptom: Your application throws an exception when trying to open or save documents.
Causes:
- File path is incorrect (typos, wrong directory)
- File is already open in another application (like Adobe Reader)
- Insufficient permissions to read input file or write to output directory
- Network path is unavailable (if working with shared drives)
Solutions:
// Always validate file existence before processing
File inputFile = new File(filePath);
if (!inputFile.exists()) {
throw new FileNotFoundException("Input document not found: " + filePath);
}
// Check if file is readable
if (!inputFile.canRead()) {
throw new IOException("Cannot read input file. Check permissions.");
}
// Ensure output directory exists and is writable
File outputDir = new File(outputDirectory);
if (!outputDir.exists()) {
outputDir.mkdirs();
}
if (!outputDir.canWrite()) {
throw new IOException("Cannot write to output directory. Check permissions.");
}
Pro tip: On Windows, files locked by other applications are a common issue. Consider implementing retry logic with a small delay, or notify users to close the document first.
Issue 2: Events Not Firing
Symptom: You’ve subscribed to events, but your handler methods never get called.
Causes:
- Events subscribed after the signing operation started
- Handler not properly registered (syntax error in subscription)
- Exception in event handler preventing subsequent handlers from running
Solutions:
// WRONG: Subscribing after signing
signature.sign(outputPath, options);
signature.SignCompleted.add(handler); // Too late!
// CORRECT: Subscribe before signing
signature.SignCompleted.add(handler);
signature.sign(outputPath, options);
// Also, wrap handler code in try-catch to prevent one handler from breaking others
public static void onSignCompleted(Signature sender, ProcessCompleteEventArgs args) {
try {
System.out.println("Signing completed: " + args.getSignatureDefinition().getSignatureType());
// Your logic here
} catch (Exception ex) {
System.err.println("Error in completion handler: " + ex.getMessage());
// Log but don't rethrow—let other handlers run
}
}
Issue 3: Signature Position Wrong or Invisible
Symptom: The signature appears in the wrong place or doesn’t show up at all.
Causes:
- Coordinates are outside document boundaries
- Font size too small or color matches background
- Signature positioned off-page
- Document has multiple pages but signature only on first page
Solutions:
// Check document dimensions before positioning
// For PDFs with multiple pages:
TextSignOptions options = new TextSignOptions("Approved");
options.setLeft(50);
options.setTop(50);
options.setWidth(200); // Set explicit width
options.setHeight(50); // Set explicit height
// Make signature visible
options.setForeColor(java.awt.Color.BLACK);
options.setFont(new SignatureFont());
options.getFont().setSize(14); // Readable size
// Apply to all pages (or specify pages)
options.setAllPages(true); // Sign every page
// OR
options.setPagesSetup(new PagesSetup());
options.getPagesSetup().setFirstPage(true); // Just first page
Pro tip: During development, use high-contrast colors (like red) for testing. This makes it obvious if positioning is off. Switch to professional colors (black/blue) for production.
Issue 4: OutOfMemoryError with Large Documents
Symptom: Application crashes when processing large PDFs or batch operations.
Causes:
- Insufficient heap memory for document processing
- Loading entire document into memory at once
- Not disposing of
Signature
objects properly
Solutions:
// Increase JVM heap size when running your application
// java -Xmx2g -jar your-app.jar
// Always dispose of Signature objects
Signature signature = null;
try {
signature = new Signature(filePath);
signature.sign(outputPath, options);
} finally {
if (signature != null) {
signature.dispose(); // Free resources
}
}
// For batch processing, process one at a time
for (String docPath : documentPaths) {
try (Signature sig = new Signature(docPath)) {
sig.sign(outputPath, options);
} // Auto-disposed after each document
// Optional: Give GC a chance to clean up
System.gc();
}
Best Practices for Production
Moving from development to production requires extra care. Here are the essential practices for deploying document signing in real applications.
1. Error Handling and Logging
Always wrap signing operations in comprehensive error handling:
public static void signDocumentSafely(String inputPath, String outputPath) {
Signature signature = null;
try {
signature = new Signature(inputPath);
// Set up event handlers with error handling
signature.SignCompleted.add((sender, args) -> {
if (args.getSucceeded()) {
logger.info("Successfully signed document: " + inputPath);
} else {
logger.error("Signing failed for: " + inputPath);
}
});
TextSignOptions options = new TextSignOptions("Approved");
options.setLeft(100);
options.setTop(100);
signature.sign(outputPath, options);
} catch (Exception ex) {
logger.error("Error signing document: " + inputPath, ex);
// Handle error appropriately—notify user, retry, etc.
throw new DocumentSigningException("Failed to sign document", ex);
} finally {
if (signature != null) {
signature.dispose();
}
}
}
2. Resource Management
Use try-with-resources when possible (requires implementing AutoCloseable):
// Clean approach for automatic resource cleanup
try (Signature signature = new Signature(filePath)) {
signature.sign(outputPath, options);
} // Automatically disposed
3. Configuration Management
Don’t hardcode file paths or signature settings:
// Load from configuration file or environment variables
String inputDir = System.getenv("DOCUMENT_INPUT_DIR");
String outputDir = System.getenv("DOCUMENT_OUTPUT_DIR");
String signatureText = config.getProperty("signature.text");
int signatureX = Integer.parseInt(config.getProperty("signature.x", "100"));
int signatureY = Integer.parseInt(config.getProperty("signature.y", "100"));
4. Validation Before Processing
Always validate inputs before attempting to sign:
public static void validateBeforeSigning(String inputPath) throws ValidationException {
File file = new File(inputPath);
// Check existence
if (!file.exists()) {
throw new ValidationException("Document does not exist: " + inputPath);
}
// Check file type (only allow specific extensions)
String fileName = file.getName().toLowerCase();
if (!fileName.endsWith(".pdf") && !fileName.endsWith(".docx")) {
throw new ValidationException("Unsupported file type. Only PDF and DOCX allowed.");
}
// Check file size (avoid processing huge files)
long maxSize = 50 * 1024 * 1024; // 50 MB
if (file.length() > maxSize) {
throw new ValidationException("File too large. Maximum size is 50 MB.");
}
}
5. Thread Safety for Concurrent Operations
If you’re signing multiple documents concurrently, ensure thread safety:
// Use ExecutorService for controlled concurrency
ExecutorService executor = Executors.newFixedThreadPool(4);
List<String> documents = Arrays.asList("doc1.pdf", "doc2.pdf", "doc3.pdf");
List<Future<?>> futures = new ArrayList<>();
for (String doc : documents) {
Future<?> future = executor.submit(() -> {
signDocumentSafely(doc, getOutputPath(doc));
});
futures.add(future);
}
// Wait for all to complete
for (Future<?> future : futures) {
future.get(); // Throws exception if signing failed
}
executor.shutdown();
When to Use Text Signatures vs. Digital Signatures
Not sure which type of signature to use? Here’s a quick decision guide:
Use Text Signatures when:
- You need visual markers or stamps (“DRAFT”, “CONFIDENTIAL”, “PAGE 1 OF 5”)
- Creating approval workflows where the signature is for tracking, not legal verification
- Adding metadata or labels to documents
- Marking documents for routing or categorization
- Legal verification isn’t required
Use Digital Signatures when:
- Legal validity and non-repudiation are required
- You need to prove document integrity (detect tampering)
- Implementing e-signature workflows with regulatory compliance (eIDAS, ESIGN Act)
- Signing contracts, agreements, or legally binding documents
- Verifying signer identity is critical
Can you use both? Absolutely! Many workflows combine them—digital signatures for legal validity and text signatures for visible markers. For example, a contract might have a digital signature for verification plus a text stamp showing “Signed by John Doe on 2025-01-02”.
Performance Optimization Tips
If you’re processing lots of documents or working with large files, these optimizations can significantly improve performance:
1. Batch Processing Strategy
// Instead of signing one at a time sequentially:
for (String doc : documents) {
signDocument(doc); // Slow for large batches
}
// Use parallel streams for independent operations:
documents.parallelStream().forEach(doc -> {
signDocument(doc);
});
2. Reuse Configuration Objects
// Create signature options once
TextSignOptions options = new TextSignOptions("Approved");
options.setLeft(100);
options.setTop(100);
// ... other settings ...
// Reuse for multiple documents
for (String doc : documents) {
try (Signature sig = new Signature(doc)) {
sig.sign(getOutputPath(doc), options); // Reuse same options
}
}
3. Monitor Performance with Events
Use event handlers to identify slow operations:
private static long startTime;
signature.SignStarted.add((sender, args) -> {
startTime = System.currentTimeMillis();
});
signature.SignCompleted.add((sender, args) -> {
long duration = System.currentTimeMillis() - startTime;
if (duration > 5000) { // Took longer than 5 seconds
logger.warn("Slow signing operation detected: " + duration + "ms");
}
});
Conclusion
You’ve now got a solid foundation for implementing document signing in Java with real-time event tracking. Here’s what you’ve learned:
- How to set up GroupDocs.Signature for Java in your project
- Implementing event handlers for start, progress, and completion events
- Adding text signatures to documents with customizable positioning
- Common pitfalls and how to avoid them
- Best practices for production deployments
- When to use text vs. digital signatures
Next Steps to Level Up:
Experiment with signature customization: Try different fonts, colors, rotations, and transparency levels to match your brand or requirements.
Explore other signature types: GroupDocs.Signature supports digital signatures, image signatures, QR codes, barcodes, and more. Each has its own use cases and configuration options.
Build a complete workflow: Combine signing with document generation, PDF manipulation, or workflow automation tools to create end-to-end solutions.
Implement batch processing: Scale up to handle hundreds or thousands of documents with proper error handling and retry logic.
Add user interfaces: Build web or desktop UIs that let non-technical users trigger signing operations with custom parameters.
Resources for deeper learning:
- GroupDocs.Signature Documentation - Complete API reference
- Support Forum - Get help from the community