spring-projects/spring-security · error · IllegalStateException
Could not coerce + source + into a URI String
Error message
Could not coerce + source + into a URI String
What it means
MappedJwtClaimSetConverter's issuer conversion found an `iss` claim that looks like a URI (contains ':') but cannot be parsed by java.net.URI, so it throws this IllegalStateException. The converter normalizes the issuer claim to a URI string; an unparseable value means the token's issuer claim is malformed.
Solutions
- Fix the authorization server's issuer setting to a valid absolute URI (RFC 3986), e.g. https://issuer.example.com.
- If the token comes from a third party you can't fix, replace the default converter: MappedJwtClaimSetConverter.withDefaults(Map.of("iss", claim -> desiredValue)) to normalize or drop the claim.
- Sanitize/validate issuer claims at token creation time so malformed values never reach the decoder.
Example fix
// before
Jwt.withTokenValue(token).claim("iss", "my issuer: v1") // invalid URI
// after
Jwt.withTokenValue(token).claim("iss", "https://my-issuer.example.com")
// or override conversion:
// MappedJwtClaimSetConverter.withDefaults(Map.of("iss", claims -> "https://my-issuer.example.com")) Defensive patterns
Strategy: validation
Validate before calling
Object iss = jwt.getClaims().get("iss");
if (iss instanceof String s && s.contains(":")) {
try { new URI(s); } catch (URISyntaxException e) { /* reject/normalize before decoding */ }
} Type guard
boolean isValidIssuerUri(Object iss) {
if (!(iss instanceof String s) || !s.contains(":")) return false;
try { new URI(s); return true; } catch (URISyntaxException e) { return false; }
} Try / catch
try { jwtDecoder.decode(token); }
catch (IllegalStateException e) {
if (e.getMessage().startsWith("Could not coerce")) { /* reject token: malformed iss claim */ }
} Prevention
- Configure the auth server issuer as a strict RFC 3986 absolute URI
- Validate iss values at token-issuance time with new URI(iss)
- Override the iss converter via MappedJwtClaimSetConverter.withDefaults if you must tolerate non-URI issuers
When it happens
Trigger: MappedJwtClaimSetConverter.convert invoked on a Jwt whose `iss` claim is a String containing ':' (e.g. 'http://:bad url', values with illegal characters/spaces) and new URI((String) source) throws URISyntaxException.
Common situations: Authorization server misconfigured with an issuer containing spaces or invalid characters; claims smuggled/overwritten by custom token enhancers; tests constructing Jwt with arbitrary iss strings like 'test: value'.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- Failed to encode the JWT due to signing error: Unable to…
- An error occurred while attempting to decode the Jwt…
- An error occurred while attempting to decode the Jwt
- An error occurred while attempting to decode the Jwt…
- An error occurred while attempting to decode the Jwt…
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/ee04411a9512f097.
Report an issue: GitHub.
Appendix: source
Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverter.java:145
Instant result = (Instant) CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, INSTANT_TYPE_DESCRIPTOR);
Assert.state(result != null, () -> "Could not coerce " + source + " into an Instant");
return result;
}
private static @Nullable String convertIssuer(Object source) {
if (source == null) {
return null;
}
URL result = (URL) CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, URL_TYPE_DESCRIPTOR);
if (result != null) {
return result.toExternalForm();
}
if (source instanceof String && ((String) source).contains(":")) {
try {
return new URI((String) source).toString();
}
catch (Exception ex) {
throw new IllegalStateException("Could not coerce " + source + " into a URI String", ex);
}
}
return (String) CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, STRING_TYPE_DESCRIPTOR);
}
@Override
public Map<String, Object> convert(Map<String, Object> claims) {
Assert.notNull(claims, "claims cannot be null");
Map<String, Object> mappedClaims = new HashMap<>(claims);
for (Map.Entry<String, Converter<Object, ? extends @Nullable Object>> entry : this.claimTypeConverters
.entrySet()) {
String claimName = entry.getKey();
Converter<Object, ? extends @Nullable Object> converter = entry.getValue();
Object claim = claims.get(claimName);
@SuppressWarnings("NullAway")
Object mappedClaim = converter.convert(claim);
mappedClaims.compute(claimName, (key, value) -> mappedClaim);
}View on GitHub (pinned to 96852e8860)