JJWT 0.12 and later expose a fluent builder and parser — Jwts.builder()…signWith(key, alg) and Jwts.parser().verifyWith(key)…parseSignedClaims(token) — with Jwts.SIG holding the algorithms. It ships as three artifacts: jjwt-api at compile time, jjwt-impl and a JSON binding such as jjwt-jackson at run time. 0.13.0 round-tripped HS, RS, PS and ES at 256/384/512 and EdDSA; ES256K and the Ed25519 and Ed448 names are not in Jwts.SIG.
The parser derives the allowed algorithm from the verification key, and its errors say so plainly. An RS256 token against an EC public key, and an HS256 token keyed with the EC key's PEM text, both failed with UnsupportedJwtException explaining that the key may not be used for that algorithm. The protection is the key type, not a list: a parser given Keys.hmacShaKeyFor(pemBytes) verified that forged token, so never build HMAC keys from material that could be public.
Unsigned tokens are refused by parseSignedClaims with a message citing RFC 7518 §3.6, and readable only through Jwts.parser().unsecured()…parseUnsecuredClaims. Weak HMAC keys never get that far: Keys.hmacShaKeyFor rejected a 16-byte key with WeakKeyException, as RFC 7518 requires keys at least as long as the hash. ES256 refuses a P-384 key at signing time.
Two defaults need attention. There is no clock skew allowance — a token expired three seconds earlier failed — and exp is optional, so set clockSkewSeconds and check expiry and issuer as the sample does. ES256 signatures are the 64-byte raw r‖s; a DER signature is reported precisely (Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes)), one of the few libraries that names the encoding problem. Inspect tokens in the JWT debugger and HMAC keys with the JWT secret checker.
For JWE and JWK set handling at scale, compare with Nimbus JOSE + JWT.