Java code signs through java.security.Signature, KeyPairGenerator and KeyFactory, and the JDK's own providers do the work: SunEC for elliptic curves and EdDSA, plus the newer ML-KEM (javax.crypto.KEM, JDK 24) and ML-DSA providers. Android, Kotlin and every JVM framework sit on the same API, so its defaults surface in Spring, Keycloak and countless SDKs.
The algorithm name decides the encoding. SHA256withECDSA produces and expects ASN.1 DER; SHA256withECDSAinP1363Format uses the 64-byte raw r‖s that JWS and WebCrypto need, as in the sample. Mixing them up behaves asymmetrically: a raw signature given to SHA256withECDSA throws SignatureException, while a DER signature given to the P1363 variant just returns false. NONEwithECDSA signs a digest you computed — its signature verified with SHA256withECDSA against the message — and the hash name is independent of the curve, so SHA256withECDSA on a P-384 key is legal and fails against peers that expect SHA-384.
SunEC in JDK 25 offers only secp256r1, secp384r1 and secp521r1 for ECDSA: P-224, secp256k1 and the Brainpool curves raised errors, which is why Bitcoin and Ethereum code on the JVM uses Bouncy Castle or web3j. Nonces are random, s is not normalized and verification accepts both halves, so signatures for strict low-S peers need s replaced by n − s.
Keys come in as X.509 SPKI DER (X509EncodedKeySpec), PKCS#8 DER (PKCS8EncodedKeySpec) or raw components (ECPrivateKeySpec). PEM support arrived as a preview API in JDK 25 (java.security.PEMDecoder, JEP 470), which decoded a public key in the check only with --enable-preview; production code still strips the armour and base64-decodes. Post-quantum support is real: ML-KEM-512/768/1024 encapsulated and ML-DSA-44/65/87 signed and verified; SLH-DSA is not in this JDK.
Compare signature encodings with the DER ⇄ raw converter, and inspect signed JARs with the JAR signature inspector.