jwtk/jjwt · error · java.lang.IllegalStateException

Both 'keyLocator' and a 'verifyWith' key cannot be configure

Error message

Both 'keyLocator' and a 'verifyWith' key cannot be configured. Prefer 'keyLocator' if possible.

What it means

JwtParser.build() rejects configuring both a keyLocator (dynamic key lookup strategy) and an explicit verifyWith key. These are two alternative mechanisms for obtaining the verification key, so having both is ambiguous and the builder fails fast with IllegalStateException. The message advises preferring keyLocator when possible.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java:385

        if (this.deserializer == null) {
            //noinspection unchecked
            json(Services.get(Deserializer.class));
        }
        if (this.signingKeyResolver != null && this.signatureVerificationKey != null) {
            String msg = "Both a 'signingKeyResolver and a 'verifyWith' key cannot be configured. " +
                    "Choose either, or prefer `keyLocator` when possible.";
            throw new IllegalStateException(msg);
        }
        if (this.keyLocator != null) {
            if (this.signatureVerificationKey != null) {
                String msg = "Both 'keyLocator' and a 'verifyWith' key cannot be configured. " +
                        "Prefer 'keyLocator' if possible.";
                throw new IllegalStateException(msg);
            }
            if (this.decryptionKey != null) {
                String msg = "Both 'keyLocator' and a 'decryptWith' key cannot be configured. " +
                        "Prefer 'keyLocator' if possible.";
                throw new IllegalStateException(msg);
            }
        }

        Locator<? extends Key> keyLocator = this.keyLocator; // user configured default, don't overwrite to ensure further build() calls work as expected
        if (keyLocator == null) {
            keyLocator = new ConstantKeyLocator(this.signatureVerificationKey, this.decryptionKey);
        }

        if (!unsecured && unsecuredDecompression) {
            String msg = "'unsecuredDecompression' is only relevant if 'unsecured' is also " +
                    "configured. Please read the JavaDoc of both features before enabling either " +
                    "due to their security implications.";
            throw new IllegalStateException(msg);
        }
        if (this.compressionCodecResolver != null && !Jwts.ZIP.get().equals(this.zipAlgs)) {
            String msg = "Both 'zip()' and 'compressionCodecResolver' " +
                    "cannot be configured. Choose either.";
            throw new IllegalStateException(msg);

View on GitHub (pinned to fb71496164)

Solutions

  1. Remove verifyWith(key) and rely solely on keyLocator
  2. Remove keyLocator(...) if a fixed verification key is sufficient and keep verifyWith(key)
  3. Audit builder call sites so only one key-source mechanism is applied

Example fix

// before
JwtParser parser = Jwts.parser()
    .keyLocator(locator)
    .verifyWith(publicKey)
    .build();
// after
JwtParser parser = Jwts.parser()
    .keyLocator(locator)
    .build();
Defensive patterns

Strategy: validation

Validate before calling

if (keyLocator != null && verificationKey != null) {
    throw new IllegalArgumentException("Use either keyLocator or verifyWith, not both");
}

Try / catch

try {
    JwtParser p = Jwts.parser().keyLocator(locator).build();
} catch (IllegalStateException e) {
    log.error("Parser key config conflict: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Calling JwtParserBuilder.keyLocator(locator) together with verifyWith(key) on the same builder instance, then calling build().

Common situations: Incrementally adopting the keyLocator API while an older verifyWith call remains; framework code that injects a locator and application code that also sets a static key; reusing a parser builder across environments where one path sets each.

Related errors


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