EdDSA (Edwards-Curve Digital Signature Algorithm) is a digital signature algorithm, and Java EdDSA support covers the Ed25519 and Ed448 curves through the standard KeyPairGenerator and Signature classes, with no extra library. A signature proves that a message comes from the holder of a private key and that nobody changed it afterwards.
We use EdDSA to sign license files, webhook payloads, software updates and tokens, wherever the receiver must check who sent the data. Compared with RSA, Ed25519 keys and signatures are small, and signing gives the same result every time, so there is no random number to get wrong.
The following example generates an Ed25519 key pair, signs a message and verifies the signature with the JDK only.
KeyPair keyPair = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
byte[] message = "order 42 paid".getBytes(StandardCharsets.UTF_8);
Signature signer = Signature.getInstance("Ed25519");
signer.initSign(keyPair.getPrivate());
signer.update(message);
byte[] signature = signer.sign();
Signature verifier = Signature.getInstance("Ed25519");
verifier.initVerify(keyPair.getPublic());
verifier.update(message);
boolean valid = verifier.verify(signature); // true
int signatureBytes = signature.length; // 64
Notice that the private key signs and the public key verifies. We cover how EdDSA works, how to store and load the keys, Ed448, a license file example, and how EdDSA compares with ECDSA, RSA and the new ML-DSA algorithm.
1. How EdDSA Signing and Verification Work
A digital signature uses a key pair. The sender keeps the private key secret and signs the bytes of a message with it. Everyone who has the matching public key can verify the signature, and the check fails if a single byte of the message or the signature changed.

EdDSA signs data, it does not encrypt it. The message travels in plain text next to its signature. When the content must stay secret as well, we encrypt it separately, for example with AES-256-GCM.
Java has three standard algorithm names for EdDSA. The specific names fix the curve, and the generic name EdDSA accepts keys of both curves.
| Algorithm name | Curve | Public key (X.509) | Signature | Security level |
|---|---|---|---|---|
| Ed25519 | Curve25519 | 44 bytes | 64 bytes | about 128 bits |
| Ed448 | Curve448 | 69 bytes | 114 bytes | about 224 bits |
| EdDSA | either, from the key or NamedParameterSpec | as above | as above | as above |
2. Generating an Ed25519 Key Pair
The KeyPairGenerator for Ed25519 needs no key size, because the curve fixes it. Calling initialize(256) as we would for EC keys throws an InvalidParameterException.
KeyPairGenerator generator = KeyPairGenerator.getInstance("Ed25519");
KeyPair pair = generator.generateKeyPair();
String algorithm = pair.getPublic().getAlgorithm(); // "EdDSA"
String publicFormat = pair.getPublic().getFormat(); // "X.509"
String privateFormat = pair.getPrivate().getFormat(); // "PKCS#8"
int publicSize = pair.getPublic().getEncoded().length; // 44
int privateSize = pair.getPrivate().getEncoded().length; // 48
KeyPairGenerator sized = KeyPairGenerator.getInstance("Ed25519");
sized.initialize(256); // InvalidParameterException: Unsupported size: 256
The algorithm of the generated keys is the family name EdDSA, not Ed25519. To find the curve of a key, we cast it to EdECKey and read getParams().getName(), as section 5 shows.
3. Signing and Verifying a Message
In an app, signing and verifying live in two different places, so we wrap them in two small methods. Both take the message as a String and convert it to UTF-8 bytes, because the signature covers bytes, and the two sides must use the same encoding.
static byte[] sign(PrivateKey key, String message) throws GeneralSecurityException {
Signature signature = Signature.getInstance("Ed25519");
signature.initSign(key);
signature.update(message.getBytes(StandardCharsets.UTF_8));
return signature.sign();
}
static boolean verify(PublicKey key, String message, byte[] sig) throws GeneralSecurityException {
Signature signature = Signature.getInstance("Ed25519");
signature.initVerify(key);
signature.update(message.getBytes(StandardCharsets.UTF_8));
return signature.verify(sig);
}
KeyPair keys = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
byte[] sig = sign(keys.getPrivate(), "refund 15.00 EUR");
boolean original = verify(keys.getPublic(), "refund 15.00 EUR", sig); // true
boolean tampered = verify(keys.getPublic(), "refund 95.00 EUR", sig); // false
boolean same = Arrays.equals(sig, sign(keys.getPrivate(), "refund 15.00 EUR")); // true
A changed amount makes verify() return false, so we treat false as a rejected message and never as a warning. The last line shows that EdDSA is deterministic, which means that the same key and message always give the same signature. ECDSA mixes a random value into every signature, and a weak random value there can leak the private key.
4. Saving and Loading Ed25519 Keys
Keys are useful only if we can store them and load them again. The encoded public key uses the X.509 SubjectPublicKeyInfo format, and the private key uses PKCS#8, which are the same formats that OpenSSL writes in PEM files between the BEGIN and END lines. For text storage, we encode the bytes with Base64.
KeyPair original = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
String publicText = Base64.getEncoder().encodeToString(original.getPublic().getEncoded());
String privateText = Base64.getEncoder().encodeToString(original.getPrivate().getEncoded());
KeyFactory factory = KeyFactory.getInstance("Ed25519");
PublicKey publicKey = factory.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicText)));
PrivateKey privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateText)));
boolean restored = publicKey.equals(original.getPublic()); // true
int publicTextLength = publicText.length(); // 60
The public key can go into a config file or the app itself. The private key belongs in a key store, a secrets manager or an environment variable, never in source control. A Java KeyStore of type PKCS12 can hold an EdDSA private key together with its certificate.
5. Ed448 and the Generic EdDSA Name
Ed448 works the same way with a larger curve, longer keys and a 114-byte signature. We use it when a standard or a partner requires a higher security level, while Ed25519 is the common default.
KeyPair ed448 = KeyPairGenerator.getInstance("Ed448").generateKeyPair();
Signature ed448Signer = Signature.getInstance("Ed448");
ed448Signer.initSign(ed448.getPrivate());
ed448Signer.update("firmware 2.1".getBytes(StandardCharsets.UTF_8));
int ed448Bytes = ed448Signer.sign().length; // 114
With the generic name EdDSA, we pick the curve through NamedParameterSpec. A Signature created with EdDSA accepts keys of either curve, while a Signature for Ed25519 rejects an Ed448 key.
KeyPairGenerator generic = KeyPairGenerator.getInstance("EdDSA");
generic.initialize(NamedParameterSpec.ED448);
KeyPair genericPair = generic.generateKeyPair();
String curve = ((EdECKey) genericPair.getPublic()).getParams().getName(); // "Ed448"
Signature strict = Signature.getInstance("Ed25519");
strict.initVerify(genericPair.getPublic()); // InvalidKeyException: Parameters must be Ed25519
6. Signing a License File
Say a desktop app sells yearly licenses. The license server signs a small text file with the customer name and expiry date, and the app ships with the public key only. At startup, the app verifies the file before it unlocks paid features, so a user who edits the expiry date gets a rejected license.
static String issueLicense(PrivateKey key, String content) throws GeneralSecurityException {
String sig = Base64.getEncoder().encodeToString(sign(key, content));
return content + "\nsignature=" + sig;
}
static boolean isLicenseValid(PublicKey key, String file) throws GeneralSecurityException {
int split = file.lastIndexOf("\nsignature=");
if (split < 0) {
return false;
}
String content = file.substring(0, split);
byte[] sig = Base64.getDecoder().decode(file.substring(split + 11));
return verify(key, content, sig);
}
KeyPair vendor = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
String license = issueLicense(vendor.getPrivate(), "customer=Ana\nexpires=2027-10-10");
boolean genuine = isLicenseValid(vendor.getPublic(), license); // true
boolean edited = isLicenseValid(vendor.getPublic(), license.replace("2027", "2099")); // false
boolean unsigned = isLicenseValid(vendor.getPublic(), "customer=Ana\nexpires=2099-01-01"); // false
The file stays readable for support staff, and the 88-character Base64 signature is short enough for an email. The same pattern works for webhook payloads, where the receiver verifies the body before it processes the event.
7. EdDSA vs ECDSA vs RSA vs ML-DSA
The JDK offers several signature algorithms, and each fits a different situation. The key and signature sizes in the table are those of the default JDK 25 providers.
| Property | Ed25519 | ECDSA P-256 | RSA 2048 | ML-DSA-65 |
|---|---|---|---|---|
| Signature name | Ed25519 | SHA256withECDSA | SHA256withRSA | ML-DSA |
| Public key (X.509) | 44 bytes | 91 bytes | 294 bytes | 1,974 bytes |
| Signature | 64 bytes | 70 to 72 bytes | 256 bytes | 3,309 bytes |
| Same signature every time | Yes | No | Yes | No (hedged by default) |
| In the JDK since | Java 15 | Java 7 | Java 5 or earlier | Java 24 |
| Safe against future quantum computers | No | No | No | Yes |
Ed25519 is a good default for new systems that control both sides. ECDSA and RSA are still needed when a partner, a hardware token or an older certificate chain requires them. ML-DSA is a post-quantum signature that Java added in Java 24, and its large keys and signatures are the price of that protection.
8. EdDSA Support Across Java Releases
Java 15 added EdDSA to the SunEC provider with JEP 339, based on RFC 8032. The implementation is written in Java and runs in constant time with respect to the secret values, so the time a signature takes does not reveal the key. Our Java features list shows the other security changes per release.
| Java version | Change |
|---|---|
| Java 15 | EdDSA with Ed25519 and Ed448 (JEP 339) |
| Java 24 | ML-DSA post-quantum signatures (JEP 497) |
| Java 25 | PEM encoding and decoding API in preview (JEP 470) |
9. Java EdDSA FAQs
Ed25519 in Java raises a few practical questions about libraries, encryption, PEM files and JWTs.
9.1. Do We Need Bouncy Castle for Ed25519 in Java?
No. Since Java 15, the built-in SunEC provider supports Ed25519 and Ed448. Bouncy Castle is needed only on Java 14 and older, or for features the JDK does not have.
9.2. Can EdDSA Encrypt Data?
No. EdDSA only signs and verifies. For encryption, we use a cipher such as AES, and for key agreement with Curve25519 the JDK offers the separate XDH algorithm, also called X25519.
9.3. How Do We Load an Ed25519 Public Key From a PEM File?
We remove the BEGIN and END lines, decode the Base64 text in between, and pass the bytes to X509EncodedKeySpec, as in section 4. Java 25 also has a PEMDecoder class, but it is a preview API that needs –enable-preview.
9.4. Can We Sign a JWT With Ed25519?
Yes. The JOSE standards define the algorithm name EdDSA for JSON Web Tokens, and libraries such as JJWT sign and verify such tokens with the JDK keys.
10. Conclusion
Java signs and verifies with EdDSA through KeyPairGenerator, Signature and KeyFactory, using the names Ed25519, Ed448 or EdDSA. Keys need no size, the public key encodes to 44 bytes in X.509 format, and every Ed25519 signature is 64 bytes long.
Signing is deterministic, verification returns false for any changed byte, and the keys load back with X509EncodedKeySpec and PKCS8EncodedKeySpec. For new code that controls both sides, Ed25519 is a small and fast default, and ML-DSA is the option when quantum safety matters.
11. References
- JEP 339, Edwards-Curve Digital Signature Algorithm
- RFC 8032, EdDSA
- Java Security Standard Algorithm Names (Java 25)
- Signature Javadoc
Happy Learning !!
Getting an exception
java.security.NoSuchAlgorithmException: Ed25519 KeyPairGenerator not available
What java version is used and is there a specific maven dependency required?
The very first line says it clearly. It is part of Java 15.