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
- Remove verifyWith(key) and rely solely on keyLocator
- Remove keyLocator(...) if a fixed verification key is sufficient and keep verifyWith(key)
- 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
- Choose keyLocator as the single key mechanism when keys rotate
- Remove legacy verifyWith calls when introducing keyLocator
- Unit-test parser construction in CI to catch config regressions
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
- Both 'zip()' and 'compressionCodecResolver' cannot be config
- 'unsecuredDecompression' is only relevant if 'unsecured' is
- Unsupported ${id} ${algType} algorithm... is disabled (no ${
- Unexpected unsecured Claims JWT.
- Unexpected content JWS.
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/9efc19e6bef36a34.
Report an issue: GitHub.