One of the most common features in document signing applications is the ability to show a visual representation of a user's signature—essentially, an image—on the final signed PDF.
In this article, we’ll walk through how to achieve that using Apache PDFBox and a base64-encoded PNG image. This approach is based on the official PDFBox CreateVisibleSignature2
example, with a few key differences:
What's Different in This Approach?
- Static image: Instead of generating the signature image dynamically, we use a pre-generated base64-encoded PNG.
- Coordinate system: We position the image starting from the bottom-left corner of the page, which is more intuitive—most signatures appear at the bottom of documents.
Full Java Example
Below is a working example that loads a base64-encoded PDF, applies a base64-encoded signature image at a given position on the first page, and prepares the document for signing:
public class VisualSigner {
public void addVisibleSignatureToPdf() throws IOException {
String pdfInBase64 = "JVBERi0xLjQKJdPr....jEwODg5CiUlRU9G";
String signatureImageInBase64 = "iVBORw0KGgoAAAA...gAAAAASUVORK5CYII=";
long signatureTime = 1692617329341L;
PDDocument document = PDDocument.load(Base64.getDecoder().decode(pdfInBase64));
PDSignature signature = new PDSignature();
signature.setType(COSName.getPDFName("Sig"));
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
Calendar cal = Calendar.getInstance();
cal.setTime(new Date(signatureTime));
signature.setSignDate(cal);
SignatureOptions options = new SignatureOptions();
options.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE);
int pageNum = 0;
options.setPage(pageNum);
Rectangle2D rect = new Rectangle2D.Float(20, 10, 200, 100);
PDRectangle pdfRect = createSignatureRectangle(document, rect);
options.setVisualSignature(createVisualSignatureTemplate(document, pageNum, pdfRect, signatureImageInBase64));
document.addSignature(signature, options);
try (FileOutputStream output = new FileOutputStream("prepared.pdf")) {
document.saveIncrementalForExternalSigning(output);
}
document.close();
IOUtils.closeQuietly(options);
}
// (Helper methods continue below...)
}
The helper methods createSignatureRectangle
and createVisualSignatureTemplate
handle coordinate alignment and image rendering. (See the full code in the repo or below.)
Understanding PDFBox Coordinates
In createSignatureRectangle
, we position the rectangle using:
rect.setLowerLeftX(...);
rect.setLowerLeftY(...);
rect.setUpperRightX(...);
rect.setUpperRightY(...);
This defines the rectangle where the signature image will appear. Here’s how it works visually:
(Insert diagram showing bottom-left origin, with arrows showing how the coordinates define a rectangle.)
- Lower-left corner defines the starting point (x, y)
- Upper-right corner defines the end (x + width, y + height)
The rotation of the page is also taken into account, ensuring the signature always appears correctly oriented.
Why Use a Base64-Encoded Image?
Using base64 allows flexibility in how the image is sourced. You can:
- Store it in a database
- Embed it in an API payload
- Generate it on the client (e.g., from a canvas) and pass it to the backend
No temporary files needed—just decode and apply it directly.
Final Thoughts
This method is useful when:
- You need a quick, consistent visual signature
- You don't want to generate the image server-side each time
- You need to preview or test signing flows before integrating full cryptographic operations
It also plays nicely with external signing flows, such as when the signature is applied by a remote Trust Service Provider after preparing the visual layer.