In short
To sign a JAR, run jarsigner -keystore keystore.p12 -tsa http://time.certum.pl MyApp.jar myalias. jarsigner hashes every entry in the archive, signs those hashes with your certificate's private key, and stores the result inside META-INF/. Always pass -tsa — without a timestamp the signature dies with your certificate. If the certificate is publicly trusted, the key lives on hardware, so you point jarsigner at a PKCS#11 config file instead of a keystore file.
jarsigner has shipped with every JDK since the nineties, and the core command hasn't changed much. What has changed is everything around it: keys for publicly trusted certificates now live on hardware tokens or in cloud services and are reached through PKCS#11, modern JDKs silently treat SHA-1-signed archives as unsigned, and a signature applied without a timestamp quietly expires with the certificate. This guide walks the whole path — what signing writes into the JAR, which keystore setup fits your situation, the exact commands, and the warnings jarsigner prints when something is off.
What signing a JAR actually does
Signing a JAR writes proof of origin into the archive itself: jarsigner computes a SHA-256 digest for every entry, records those digests in the manifest, then signs them with your certificate's private key and stores the signature — with your certificate chain and an optional timestamp — as new files under META-INF/. Anyone holding the JAR can later confirm both who signed it and that not a single byte has changed since.
Three files carry the whole mechanism. The manifest (MANIFEST.MF) gains a digest line for every entry in the archive. The signature file (MYALIAS.SF, named after your keystore alias) contains digests of the manifest's sections, so the manifest can't be quietly rewritten. And the signature block (MYALIAS.RSA, or .EC for elliptic-curve keys) holds the actual cryptographic signature plus the certificates needed to check it. Your classes and resources are untouched — which is also why a signed and unsigned JAR behave identically at runtime.
One detail that surprises people coming from Windows code signing: coverage is per entry, not per file-blob. If any tool adds an entry to the archive after signing, the JAR doesn't become invalid — it becomes partially signed, and verification flags it as containing unsigned entries. That's the root cause behind most mysterious verification failures in build pipelines.
When do you still need to sign a JAR?
You need to sign a JAR when something downstream verifies it: a plugin framework, an enterprise deployment policy, middleware that refuses unsigned archives, or customers who check vendor artifacts before letting them near production. The applet era that made JAR signing famous is over — browsers dropped the Java plugin years ago and Java Web Start left the JDK with version 11 — but the verification habit moved into infrastructure rather than disappearing.
- Plugin ecosystems. Eclipse p2 and other OSGi-based installers surface warnings — or refuse installation outright, depending on policy — for unsigned bundles. If you distribute Eclipse or IDE plugins, signing is part of shipping.
- Enterprise and middleware requirements. Plenty of corporate Java estates verify signatures on the JARs they deploy, and products like Oracle E-Business Suite require customizations to be signed with a trusted certificate.
- Supply-chain assurance. A signed, timestamped JAR gives customers a one-command check (
jarsigner -verify) that the artifact they downloaded is byte-for-byte what you released — independent of the download channel.
Worth being clear about what JAR signing is not: Maven Central verifies PGP signatures, not jarsigner signatures, and a desktop app packaged with jpackage gets signed at the installer level with OS-native tools — SignTool on Windows. jarsigner is specifically for the archives the Java world itself verifies.
What you need before you sign
Two things: a JDK (jarsigner and keytool live in its bin directory, no separate download) and a certificate with the code signing purpose whose private key jarsigner can reach. Where that certificate comes from depends on who must trust the result — the same three-tier logic as every other kind of code signing.
- Testing and internal tools:
keytool -genkeypair -alias myalias -keyalg RSA -keysize 3072 -validity 365 -keystore keystore.p12creates a self-signed certificate in a PKCS#12 keystore (the default format since JDK 9). Signatures verify only where you've imported that certificate into the truststore by hand. - Inside one organization: a code signing certificate from your internal CA, imported into a keystore, verifies wherever machines already trust that CA's root.
- Software that leaves your organization: you need a certificate from a publicly trusted CA, issued to a validated identity. My-SSL carries Certum standard and EV code signing certificates that jarsigner uses the same way SignTool does — Certum's signing manual documents both tools side by side. The validation paperwork is covered in the required documents guide.
No .p12 file for publicly trusted keys
Under CA/Browser Forum rules in force since June 1, 2023, publicly trusted code signing keys must be generated and kept on certified hardware — a USB token, smart card, HSM, or a cloud signing service. You will not receive a keystore file to pass to -keystore. Instead, jarsigner talks to the hardware through PKCS#11, which is what the next section sets up. The self-signed keystore path above remains available for anything that doesn't need public trust.
Sign the JAR with jarsigner
With a keystore file, one command does everything: name the keystore, name a timestamp authority with -tsa, then give the JAR and the alias of the key to sign with. jarsigner prompts for the keystore password, rewrites the archive in place (or writes a copy if you pass -signedjar), and prints jar signed. on success.
# Check which alias your keystore actually contains
keytool -list -keystore keystore.p12
# Sign in place, with a timestamp
jarsigner -keystore keystore.p12 \
-tsa http://time.certum.pl \
MyApp.jar myalias
# Or keep the original and write a signed copy
jarsigner -keystore keystore.p12 \
-tsa http://time.certum.pl \
-signedjar MyApp-signed.jar MyApp.jar myalias
# Expected output:
# jar signed.Unlike SignTool, you don't need to force the hash algorithm: a current JDK defaults to SHA-256 digests and, for RSA keys, a SHA256withRSA signature — sensible defaults you'd only override with -digestalg and -sigalg in unusual compatibility situations. The flag you should treat as mandatory is -tsa. Without it, jarsigner still signs but warns that the JAR may not validate after the certificate expires — and it means exactly that: every copy you ever shipped stops verifying on that date. Our timestamping guide covers why in detail; the short version is that the timestamp proves signing happened while the certificate was valid, which lets the signature outlive it.
The trailing myalias argument trips up more first attempts than any flag: it's the name of the key entry inside the keystore, not a file name or your company name. When in doubt, keytool -list shows every alias the keystore holds.
Signing from a USB token or the cloud (PKCS#11)
When the private key lives on a hardware token or in a cloud signing service, jarsigner reaches it through PKCS#11: you write a two-line config file pointing at the vendor's PKCS#11 library, then tell jarsigner to use that instead of a keystore file. The signing command itself barely changes, and the token PIN takes the place of the keystore password.
# provider.cfg — points Java at the token's PKCS#11 library
# (path shown for a Certum card on Windows; the SimplySign
# desktop app installs its own library)
name = Certum
library = C:\Windows\System32\crypto3PKCS.dll
# List the aliases the token exposes
keytool -list -keystore NONE -storetype PKCS11 \
-providerClass sun.security.pkcs11.SunPKCS11 \
-providerArg provider.cfg
# Sign — jarsigner prompts for the token PIN
jarsigner -keystore NONE -storetype PKCS11 \
-providerClass sun.security.pkcs11.SunPKCS11 \
-providerArg provider.cfg \
-tsa http://time.certum.pl \
MyApp.jar "certificate alias"The alias on a token is usually derived from the certificate's Common Name — your validated company or personal name, often with spaces, so quote it. If the keytool -list step shows no aliases, the token middleware isn't installed or the card isn't connected; sort that out before blaming jarsigner. The same PKCS#11 setup works whether the key sits on a physical Certum card or in Certum's SimplySign cloud service, where a desktop app stands in for the card reader — handy when the person (or CI job) signing isn't near a USB port. The trade-offs between those two setups get a full article in cloud signing vs USB tokens.
Verify the signature
Verification is one command: jarsigner -verify -verbose -certs MyApp.jar. It re-digests every entry, checks the signature and the certificate chain, and ends with jar verified. when everything holds. The -verbose -certs pair is what makes the output useful: per-entry flags, the full signer chain, and a Timestamped by line confirming your -tsa flag did its job.
jarsigner -verify -verbose -certs MyApp.jar
# Healthy output ends with:
# - Signed by "CN=Your Company, O=Your Company, C=..."
# - Timestamped by "CN=Certum Timestamping 2024 CA" on Mon Jul 06 ...
# jar verified.
# In CI, add -strict so problems become a non-zero exit code
jarsigner -verify -strict MyApp.jarRead the per-entry flags in the verbose listing: s means the entry's signature verified, m means it's listed in the manifest. An entry with neither is unsigned — the partially-signed trap from earlier. And note that plain -verify is forgiving by design; it can print jar verified. followed by warnings you'd rather treat as failures. -strict turns those warnings into exit codes, which is the behavior you want in a pipeline.
Common warnings and what they mean
jarsigner rarely fails outright — its habit is to sign or verify anyway and attach a warning, which makes the warnings worth reading carefully. These are the ones that actually stop people, roughly in the order they tend to appear.
"Certificate chain not found for: myalias"
The alias doesn't exist in the keystore you named, or it exists but has no private key. Run keytool -list against the same keystore and copy the alias exactly — it's case-insensitive but must otherwise match. On tokens, remember the alias comes from the certificate's Common Name, not anything you chose.
"keystore load: … password was incorrect" or "keystore was tampered with"
Wrong keystore password — the "tampered with" phrasing is historical JKS wording for the same thing. Against a PKCS#11 token, the "password" is the token PIN; too many wrong attempts can lock the card, so check with the vendor's card manager app rather than retrying blindly.
"no -tsa or -tsacert is provided … may not be able to validate this jar after the signer certificate's expiration"
You signed without a timestamp. The signature works today and breaks the day the certificate expires. Re-run the same command with -tsa http://time.certum.pl (any RFC 3161 timestamp authority works) — re-signing simply replaces the old signature.
"This jar contains unsigned entries" or "entries whose signer certificate … " after a build
Something modified the archive after signing — shade/assembly plugins, resource filtering, even a well-meaning re-zip. Coverage is per entry, so the added files are simply outside the signature. Make jarsigner the final build step and verify with -strict right after.
"jar treated as unsigned, because it is signed with a weak algorithm that is now disabled"
The SHA-1 policy. Since the October 2022 JDK updates, archives whose digest, signature, or timestamp uses SHA-1 are treated as unsigned unless they carry a timestamp from before January 1, 2019. You'll see this on JARs signed years ago with old defaults, or by pipelines that still pass -digestalg SHA1. The fix is to re-sign with a current JDK and a timestamp — the defaults are already right.
"The signer's certificate is self-signed"
Expected with a keytool-generated test certificate — it's a statement of fact, not an error. It disappears when you sign with a CA-issued certificate whose chain ends in a trusted root. If you're seeing it in production distribution, that's the signal you've outgrown the test setup.
The bottom line
Find your alias with keytool -list, sign with jarsigner -keystore … -tsa http://time.certum.pl MyApp.jar myalias, and confirm with jarsigner -verify -strict. Two habits carry all the long-term weight: never sign without the timestamp, and always sign as the last build step. When your JARs need to verify on machines that have never heard of you, a Certum code signing certificate plugs into this exact workflow through PKCS#11 — and the same certificate signs your Windows binaries too.