---
title: "Signature sample explained - PKI Express on Java"
description: "Walkthrough of the PAdES signature sample in the PKI Express Java samples project, listing the controller, views and JavaScript modules involved."
canonical: "https://docs.lacunasoftware.com/en-us/articles/pki-express/java/how-it-works"
locale: "en"
product: "PKI Express"
last_updated: "2026-10-12"
---

# Signature sample explained - PKI Express on Java

This article explains how the digital signature process performed in the PKI Express Java Samples project works.

## Main code files

* Controller [PadesSignatureExpressController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
* View [pades-signature-express/index](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/index.html)
* JavaScript Module [signature-start-form](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
* View [pades-signature-express/start](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/start.html)
* JavaScript Module [signature-complete-form](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)
* View [pades-signature-express/signature-info](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/signature-info.html)

## Signature process

### 1. User goes to the signature page

At this time, the user visits the URL `/pades-signature-express?fileId={fileToSign}` (`GET`). The controller `PadesSignatureExpressController` is responsible for responding to the request, more specifically in
the method `get()`:

```java
@Controller
@RequestMapping("/pades-signature-express")
public class PadesSignatureExpressController {

	/**
	 * GET /pades-signature-express?fileId={fileToSign}
	 *
	 * This action simply renders the page.
	 */
	@GetMapping
	public String get(@RequestParam(value = "fileId") String fileToSign) throws IOException {

		// Verify if the fileToSign exists.
		if (fileToSign == null || !StorageMock.exists(fileToSign)) {
			throw new FileNotFoundException();
		}

		// Render the signature page (templates/pades-signature-express/index.html)
		return "pades-signature-express/index";
	}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

At this time, we just check that the file to be signed exists and render the signature page, the view `pades-signature-express/index`.

> **Note**
>
> The parameter `fileId` and the class `StorageMock` are merely details of the example: they stand for the way your application stores its files.

Once the page has loaded, we pass references to the page elements to the JavaScript module `signatureStartForm`:

```js
$(document).ready(function () {
	// Once the page is ready, we call the init() function on the javascript code
	// (see signature-start-form.js).
	signatureStartForm.init({
		form: $('#signForm'),                       // The form that should be submitted when the operation is complete.
		certificateSelect: $('#certificateSelect'), // The <select> element (combo box) to list the certificates.
		refreshButton: $('#refreshButton'),         // The "refresh" button.
		signButton: $('#signButton'),               // The button that initiates the operation.
		certThumbField: $('#certThumbField'),       // The "certThumb" field.
		certContentField: $('#certContentField')    // The "certContent" field.
	});
});
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/index.html)

The module `signatureStartForm` blocks the screen and triggers Web PKI initialization, passing the function `loadCertificates()` as callback when the initialization has completed, and the function `onWebPkiError()` as `defaultFail`, the callback for errors on any later Web PKI operation:

```js
// ----------------------------------------------------------------------------------------------
// Initializes the signature form.
// ----------------------------------------------------------------------------------------------
function init(fe) {

	// Receive form parameters received as arguments.
	formElements = fe;

	// Wireup of buttons clicks.
	formElements.signButton.click(startSignature);
	formElements.refreshButton.click(refresh);

	// Block the UI while we get things ready.
	$.blockUI({ message: 'Initializing ...' });

	// Call the init() method on the LacunaWebPKI object, passing a callback for when the
	// component is ready to be used and another to be called when an error occurs on any of the
	// subsequent operations. For more information, see:
	// https://docs.lacunasoftware.com/en-us/articles/web-pki/get-started.html#coding-the-first-lines
	// https://webpki.lacunasoftware.com/Help/classes/LacunaWebPKI.html#method_init
	pki.init({
		ready: loadCertificates,    // As soon as the component is ready we'll load the certificates.
		defaultFail: onWebPkiError // Generic error callback (see function declaration below).
	});
}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

In the function `loadCertificates()`, we trigger the loading of the certificates available on the user's computer, populating the element `<select>` from the page:

```js
// ----------------------------------------------------------------------------------------------
// Function that loads the certificates, either on startup or when the user clicks the "Refresh"
// button. At this point, the UI is already blocked.
// ----------------------------------------------------------------------------------------------
function loadCertificates() {

	// Call the listCertificates() method to list the user's certificates.
	pki.listCertificates({

		// ID of the <select> element to be populated with the certificates:
		selectId: formElements.certificateSelect.attr('id'),

		// Function that will be called to get the text that should be displayed for each option:
		selectOptionFormatter: function (cert) {
			var s = cert.subjectName + ' (issued by ' + cert.issuerName + ')';
			if (new Date() > cert.validityEnd) {
				s = '[EXPIRED] ' + s;
			}
			return s;
		}

	}).success(function () {

		// Once the certificates have been listed, unblock the UI.
		$.unblockUI();

	});

}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

### 2. User clicks the **Sign File** button

Previously, the JavaScript module `signatureStartForm` registered JavaScript function `startSignature()` to be called when the **Sign File** button is clicked:

```js
// Wireup of buttons clicks.
formElements.signButton.click(startSignature);
formElements.refreshButton.click(refresh);
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

> **Note**
>
> It is critical that the button on the page has `type="button"`, not `type="submit"`, otherwise a postback would occur immediately upon clicking the button.

Therefore, when the user clicks the **Sign File** button, the `startSignature()` function of `signatureStartForm` is invoked:

```js
// ----------------------------------------------------------------------------------------------
// Function called when the user clicks the "Sign File" button.
// ----------------------------------------------------------------------------------------------
function startSignature() {

	// Block the UI while we perform the signature.
	$.blockUI({ message: 'Starting signature ...' });

	// Get the thumbprint of the selected certificate.
	var selectedCertThumbprint = formElements.certificateSelect.val();

	// Get certificate content to be passed to "start" action after the form submission.
	pki.readCertificate(selectedCertThumbprint).success(/* ... */);
}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

In this function, after blocking the page, we get the thumbprint of the selected certificate, which will be needed again later in the signature process. We then invoke the function `readCertificate()` of Web PKI to obtain the encoding of the chosen certificate (public part of the certificate, the ".cer", without the private key):

```js
// Get certificate content to be passed to "start" action after the form submission.
pki.readCertificate(selectedCertThumbprint).success(// ...
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

Once the certificate is read, we fill in the hidden fields of the page with the thumbprint and the certificate, and do the postback:

```js
/* ... */.success(function (certEncoded) {

	// Fill fields needed on the next steps of the signature.
	formElements.certThumbField.val(selectedCertThumbprint);
	formElements.certContentField.val(certEncoded);

	// Submit the form.
	formElements.form.submit();

});
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

### 3. Signing backend start

In the backend, the controller `PadesSignatureExpressController` is again invoked to handle the postback made from the frontend with the certificate chosen to perform the signature, done for the URL `/pades-signature-express/start` with a verb `POST`. The method `postStart()` is responsible for handling the request:

```java
/**
 * POST /pades-signature-express/start?fileId={fileToSign}
 *
 * This action receives the form submission from the signature page. It will perform a PAdES
 * signature in three steps using PKI Express and Web PKI.
 */
@PostMapping("/start")
public String postStart(
	@RequestParam(value = "fileId") String fileToSign,
	@RequestParam String certThumb,
	@RequestParam String certContent,
	Model model
) throws IOException {
	// ...
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

At this point, we start the signature using the class `PadesSignatureStarter`, passing the signature policy, the file to be signed and the encoding of the certificate chosen for signature:

```java
// Get an instance of the PadesSignatureStarter class, responsible for receiving
// the signature elements and start the signature process.
PadesSignatureStarter signatureStarter = new PadesSignatureStarter();

// Set PKI default options (see Util.java)
Util.setPkiDefaults(signatureStarter);

// Set signature policy.
signatureStarter.setSignaturePolicy(StandardSignaturePolicies.PadesBasicWithLTV);

// Set PDF to be signed.
signatureStarter.setPdfToSign(StorageMock.getDataPath(fileToSign));

// Set Base64-encoded certificate's content to signature starter.
signatureStarter.setCertificateBase64(certContent);
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

The class is created without arguments, so it uses the default `PkiExpressConfig`: PKI Express in its default installation folder and the system temporary folder (see [client library settings](https://docs.lacunasoftware.com/en-us/articles/pki-express/config#client-library)). `Util.setPkiDefaults()` sets what every operation of the sample has in common: the trusted roots, including the Lacuna test root when configured ([trusting in custom certificates](https://docs.lacunasoftware.com/en-us/articles/pki-express/java/trusting-in-custom-certificates)), the offline mode and the timestamp authority.

We then configure the signature appearance:

```java
// Set visual representation. We provide a Java class that represents the visual
// representation model.
signatureStarter.setVisualRepresentation(PadesVisualElements.getVisualRepresentation());
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

> **Note**
>
> For more details on configuration options for the appearance of the signature, see the class [PadesVisualElements](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/util/express/PadesVisualElements.java) and the article [PAdES visual representation](https://docs.lacunasoftware.com/en-us/articles/pki-express/java/visual-rep).

Finally, we conclude the initial step, obtaining the necessary data to perform the digital signature algorithm, and delete the temporary files that the starter created:

```java
// Start the signature process. Receive as response a SignatureStartResult instance
// containing the following fields:
// - toSignHash: The hash to be signed.
// - digestAlgorithm: The digest algorithm that will inform the Web PKI component
// to compute the signature.
// - transferFile: A temporary file to be passed to "complete" step.
SignatureStartResult result = signatureStarter.start();

// If you want to delete the temporary files created by this step use the method
// dispose(). This method MUST be called after the start() method, because it
// deletes some files needed by the method.
signatureStarter.dispose();
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

An important point: the class `PadesSignatureStarter` saves a file called transfer data file containing information that needs to be retrieved later in the signature process. By default,
this file is stored in the system temporary folder, but another folder can be configured with `PkiExpressConfig` ([client library settings](https://docs.lacunasoftware.com/en-us/articles/pki-express/config#client-library)). `dispose()` does not delete it, since the "complete" step needs it. The method `SignatureStartResult.getTransferFile()` returns the name of the file, which, along with the other fields of the result, is rendered in hidden fields on the page:

```java
// Render the fields received form start() method as hidden fields to be used on
// the javascript or on the "complete" step.
model.addAttribute("certThumb", certThumb);
model.addAttribute("certContent", certContent);
model.addAttribute("toSignHash", result.getToSignHash());
model.addAttribute("transferFileId", result.getTransferFile());
model.addAttribute("digestAlgorithm", result.getDigestAlgorithm());
return "pades-signature-express/start";
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

> **Note**
>
> Security concerns are taken in generating the name of this file, so it is impossible for a user to guess the transfer data file from another user's signing process. Also, only the
> filename is sent to the page, not the full path, so it is also not possible for a user to induce the web application to read a file in an arbitrary folder.

The controller returns the view `pades-signature-express/start`, which holds those hidden fields.

### 4. Realization of the digital signature algorithm in the frontend

Once the page is loaded, the `init()` function of the `signatureCompleteForm` is invoked with references to the hidden fields:

```js
$(document).ready(function () {
	// Once the page is ready, we call the init() function on the javascript code
	// (see signature-complete-form.js).
	signatureCompleteForm.init({
		form: $('#signForm'),                             // The form that should be submitted when the operation is complete.
		certThumbField: $('#certThumbField'),             // The "certThumb" field.
		toSignHashField: $('#toSignHashField'),           // The "toSignHash" field.
		digestAlgorithmField: $('#digestAlgorithmField'), // The "digestAlgorithm" field.
		signatureField: $('#signatureField')              // The "signature" field.
	});
});
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/start.html)

We then block the page and invoke the Web PKI initialization again, but this time passing the function `sign()` as callback:

```js
// ----------------------------------------------------------------------------------------------
// Initializes the signature form.
// ----------------------------------------------------------------------------------------------
function init(fe) {

	// Receive form parameters received as arguments.
	formElements = fe;

	// Block the UI while we get things ready.
	$.blockUI({ message: 'Signing ...' });

	// Call the init() method on the LacunaWebPKI object, passing a callback for when the
	// component is ready to be used and another to be called when an error occurs on any of the
	// subsequent operations. For more information, see:
	// https://docs.lacunasoftware.com/en-us/articles/web-pki/get-started.html#coding-the-first-lines
	// https://webpki.lacunasoftware.com/Help/classes/LacunaWebPKI.html#method_init
	pki.init({
		ready: sign,                 // As soon as the component is ready we'll perform the signature.
		defaultFail: onWebPkiError  // Generic error callback defined below.
	});
}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)

In the function `sign()`, we invoke the Web PKI to perform the digital signature algorithm with the private key of the certificate, using as input data the data returned by PKI Express in the backend:

```js
// ----------------------------------------------------------------------------------------------
// Function that performs the signature on startup. At this point, the UI is already blocked.
// ----------------------------------------------------------------------------------------------
function sign() {

	// Call signHash() on the Web PKI component passing the "toSignHash", the digest algorithm
	// and the certificate selected by the user.
	pki.signHash({
		thumbprint: formElements.certThumbField.val(),
		hash: formElements.toSignHashField.val(),
		digestAlgorithm: formElements.digestAlgorithmField.val()
	}).success(function (signature) {

		// Fill the "signature" field, needed on server-side to complete the signature.
		formElements.signatureField.val(signature);

		// Submit the form.
		formElements.form.submit();
	});
}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)

Once the signature algorithm is finished, we fill in the result in a hidden field in the page and make a new postback to the backend:

```js
/* ... */.success(function (signature) {

	// Fill the "signature" field, needed on server-side to complete the signature.
	formElements.signatureField.val(signature);

	// Submit the form.
	formElements.form.submit();
});
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)

### 5. Signature termination on backend

In the backend, the controller `PadesSignatureExpressController` is again invoked to handle the postback made from the frontend with the result of the digital signature algorithm, done for the URL `/pades-signature-express/complete` with a verb `POST`. The method `postComplete()` is responsible for handling the request:

```java
@PostMapping("/complete")
public String postComplete(
	@RequestParam(value = "fileId") String fileToSign,
	@RequestParam String transferFileId,
	@RequestParam String signature,
	Model model
) throws IOException {
	// ...
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

We get an instance of the `SignatureFinisher` class and provide the file being signed, the name of the transfer data file and the result of the signature algorithm done in the frontend with the Web PKI:

```java
// Get an instance of the SignatureFinisher class, responsible for completing the
// signature process.
SignatureFinisher signatureFinisher = new SignatureFinisher();

// Set PKI default options (see Util.java)
Util.setPkiDefaults(signatureFinisher);

// Set PDF to be signed. It's the same file we used on "start" step.
signatureFinisher.setFileToSign(StorageMock.getDataPath(fileToSign));

// Set transfer file.
signatureFinisher.setTransferFilePath(transferFileId);

// Set the signature value.
signatureFinisher.setSignature(signature);
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

> **Note**
>
> In the current version of the `pki-express` package (1.23.1), `setTransferFilePath(String)` is deprecated: use `setTransferFileId()`, which takes the same value.

We generate a new file name to receive the signed PDF:

```java
// Generate path for output file and add to signature finisher.
String outputFile = StorageMock.generateFileId("pdf");
signatureFinisher.setOutputFilePath(StorageMock.getDataPath(outputFile));
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

We complete the signing process and, again, delete the temporary files that the finisher created:

```java
// Complete the signature process.
signatureFinisher.complete();

// If you want to delete the temporary files created by this step, use the method
// dispose(). This method MUST be called after the complete() method, because it
// deletes some files needed by the method.
signatureFinisher.dispose();
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

After this command, the signed file is saved at the output path set above. It is then at the discretion of your application to move this file to the correct location, be it database, file system or storage service. The sample renders the view `pades-signature-express/signature-info`, with a link to download the signed file:

```java
// Render the signature page (templates/pades-signature-express/signature-info.html).
model.addAttribute("signedPdf", outputFile);
return "pades-signature-express/signature-info";
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

**Demos and samples**

- Live demo: [Sign a file with your certificate](https://try.lacunasoftware.com/en/pki-suite/sign-a-file)
