// build.gradle.ktsdependencies { implementation("io.jsonwebtoken:jjwt-api:0.13.0") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.13.0") runtimeOnly("io.jsonwebtoken:jjwt-gson:0.13.0") // or jjwt-jackson}import io.jsonwebtoken.Jwtsimport java.io.Fileimport java.security.KeyFactoryimport java.security.spec.PKCS8EncodedKeySpecimport java.time.Instantimport java.util.Base64import java.util.Date fun main() { val pem = File("key.pem").readText() val pkcs8 = Base64.getMimeDecoder().decode(pem.replace(Regex("-----[^-]+-----"), "")) val privateKey = KeyFactory.getInstance("EC").generatePrivate(PKCS8EncodedKeySpec(pkcs8)) val now = Instant.now() val token = Jwts.builder() .header().keyId("2026-10").type("JWT").and() .issuer("https://issuer.example.com") .subject("user-42") .audience().add("api://orders").and() .issuedAt(Date.from(now)) .expiration(Date.from(now.plusSeconds(15 * 60))) .signWith(privateKey, Jwts.SIG.ES256) // checks that the key is P-256 .compact() println(token)}How it works
- The PKCS#8 PEM body is loaded with
KeyFactory.getInstance("EC"); JJWT signs with any JCAPrivateKey. .header().keyId(…).type("JWT").and()writeskidandtyp;issuer,subject,audience().add(…),issuedAtandexpirationwrite the claims.signWith(privateKey, Jwts.SIG.ES256)checks that the key is a 256-bit EC key and produces the 64-byte JWS signature.- Code compiles against
jjwt-apialone;jjwt-impland a JSON module (jjwt-gsonorjjwt-jackson) are needed only at runtime.
Gotchas
- Forgetting
jjwt-implcompiles fine and fails at the firstJwts.builder():UnknownClassException: Unable to load class named [io.jsonwebtoken.impl.DefaultJwtBuilder$Supplier] … Have you remembered to include the jjwt-impl.jar in your runtime classpath?. audience().add(…)writesaudas an array even for one value.audience().single(…)writes a string but is deprecated; prefer the array unless a consumer cannot read it.- A P-384 key with
Jwts.SIG.ES256fails withSignatureException: Unable to compute ES256 signature. Cause: The provided Elliptic Curve signing key size (aka order bit length) is 384 bits (48 bytes), but the 'ES256' algorithm requires EC Keys with 256 bits (32 bytes)…. - JJWT 0.12 renamed the builder methods (
setSubject→subject,setExpiration→expiration) and replaced theSignatureAlgorithmenum withJwts.SIG. Code written for 0.11 still compiles against 0.13, with a deprecation warning on every one of those calls — update it before they are removed.