jwtk/jjwt · error · UnsupportedJwtException

Cannot decrypt JWE payload: unable to locate key for JWE…

Error message

Cannot decrypt JWE payload: unable to locate key for JWE with header: + jweHeader

What it means

Before decrypting a JWE the parser calls the configured keyLocator to obtain the decryption key for the token's header; if the locator returns null, decryption is impossible and the parser throws UnsupportedJwtException naming the header so you can see which key the token expected.

Solutions

  1. Configure a key source: call decryptWith(SecretKey/PrivateKey) or keyLocator(Locator) on the JwtParserBuilder before parsing.
  2. Ensure the locator handles the token's kid/alg and returns a matching key, or throw with a clear message instead of returning null.
  3. Verify key rotation: if the kid is unknown, load archived keys or reject the token upstream.
  4. Log the JWE header (alg, kid, enc) and confirm your key store contains the corresponding key.

Example fix

// before
JwtParser parser = Jwts.parser().build();
// after
JwtParser parser = Jwts.parser()
    .keyLocator(header -> keyStore.get(header.get("kid", String.class)))
    .build();
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure a key source is wired before parsing
Objects.requireNonNull(myDecryptionKeyOrLocator, "JWE decryption key/locator must be configured");

Try / catch

try { parser.parse(jwe); } catch (UnsupportedJwtException e) { if (e.getMessage().startsWith("Cannot decrypt JWE payload")) { /* load/refresh key for kid, then retry */ } }

Prevention

When it happens

Trigger: Parsing a JWE when no KeyLocator/Locator is configured, or the configured locator returns null for the token's alg/kid header (e.g. kid not present in the key store).

Common situations: Forgetting to call .keyLocator(...) or .decryptWith(key) on JwtParserBuilder, key rotation removing the kid the token references, multi-tenant lookup returning null for unknown issuers.

Related errors


AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09). Data as JSON: /api/errors/ae258fa576adf296. Report an issue: GitHub.

Appendix: source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:543

            // https://www.rfc-editor.org/rfc/rfc7516.html#section-5.1, Step 14.
            ByteBuffer buf = StandardCharsets.US_ASCII.encode(Strings.wrap(base64UrlHeader));
            final byte[] aadBytes = new byte[buf.remaining()];
            buf.get(aadBytes);
            InputStream aad = Streams.of(aadBytes);

            base64Url = base64UrlDigest;
            //guaranteed to be non-empty via the `alg` + digest check above:
            Assert.hasText(base64Url, "JWE AAD Authentication Tag cannot be null or empty.");
            digest = decode(base64Url, "JWE AAD Authentication Tag");
            if (Bytes.isEmpty(digest)) {
                String msg = "Compact JWE strings must always contain an AAD Authentication Tag.";
                throw new MalformedJwtException(msg);
            }

            Key key = this.keyLocator.locate(jweHeader);
            if (key == null) {
                String msg = "Cannot decrypt JWE payload: unable to locate key for JWE with header: " + jweHeader;
                throw new UnsupportedJwtException(msg);
            }
            if (key instanceof PublicKey) {
                throw new InvalidKeyException(PUB_KEY_DECRYPT_MSG);
            }

            // extract key-specific provider if necessary;
            Provider provider = ProviderKey.getProvider(key, this.provider);
            key = ProviderKey.getKey(key); // this must be called after ProviderKey.getProvider
            DecryptionKeyRequest<Key> request =
                    new DefaultDecryptionKeyRequest<>(cekBytes, provider, null, jweHeader, encAlg, key);
            final SecretKey cek = keyAlg.getDecryptionKey(request);
            if (cek == null) {
                String msg = "The '" + keyAlg.getId() + "' JWE key algorithm did not return a decryption key. " +
                        "Unable to perform '" + encAlg.getId() + "' decryption.";
                throw new IllegalStateException(msg);
            }

            // During decryption, the available Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly

View on GitHub (pinned to fb71496164)