spring-projects/spring-security · error · IllegalArgumentException
Expected number but found
Error message
Expected number but found {tokens[0]} What it means
KeyBasedPersistenceTokenService.verifyToken parses the token's ':'-separated fields and expects the first field to be a number (creation time, parsed with Long.decode). If parsing fails the token is malformed or tampered, and it throws IllegalArgumentException.
Solutions
- Regenerate the token by calling allocateToken again and have the user re-authenticate
- Verify the token was not truncated or re-encoded in transit/storage (check URL-encoding, cookie handling)
- Wrap verifyToken in try-catch (IllegalArgumentException) and treat failures as 'token invalid' rather than crashing
Example fix
// before
Token token = service.verifyToken(rawToken);
// after
try {
Token token = service.verifyToken(rawToken);
} catch (IllegalArgumentException ex) {
// invalid token: force re-authentication
service.allocateToken(user.getKey());
} Defensive patterns
Strategy: try-catch
Validate before calling
String[] tokens = rawToken.split(":"); boolean looksNumeric = tokens.length >= 4 && tokens[0].matches("(0[xX])?[0-9a-fA-F]+|[+-]?[0-9]+"); Type guard
boolean plausiblyValidToken(String t) { return t != null && t.split(":").length >= 4; } Try / catch
try { token = service.verifyToken(rawToken); } catch (IllegalArgumentException ex) { token = null; // treat as invalid: force re-auth } Prevention
- Always catch IllegalArgumentException around verifyToken
- Never hand-edit or truncate token strings
- Watch for cookie/URL encoding corrupting the ':' separators
- Regenerate tokens after key rotation
When it happens
Trigger: Calling verifyToken with a token whose first field is not a valid number — corrupted token, truncated token, a token produced by a different service/version, or manual construction of the token string.
Common situations: Remember-me / persistent tokens mangled by URL encoding or storage truncation; tampering attempts (tests verify with tampered keys); switching the secret key invalidates nothing here, but a hand-edited token does.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- An error occurred while attempting to decode the Jwt…
- An error occurred while attempting to decode the Jwt: +…
- Bad salt length
- Can not set rememberMeCookieName and custom…
- Cannot pass null or empty values to constructor
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/23e2be438a45bb1d.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java:119
String sha512Hex = Sha512DigestUtils.shaHex(content + ":" + serverSecret);
String keyPayload = content + ":" + sha512Hex;
return Utf8.decode(Base64.getEncoder().encode(Utf8.encode(keyPayload)));
}
@Override
public @Nullable Token verifyToken(String key) {
if (key == null || "".equals(key)) {
return null;
}
String[] tokens = StringUtils
.delimitedListToStringArray(Utf8.decode(Base64.getDecoder().decode(Utf8.encode(key))), ":");
Assert.isTrue(tokens.length >= 4, () -> "Expected 4 or more tokens but found " + tokens.length);
long creationTime;
try {
creationTime = Long.decode(tokens[0]);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Expected number but found " + tokens[0]);
}
String serverSecret = computeServerSecretApplicableAt(creationTime);
String pseudoRandomNumber = tokens[1];
// Permit extendedInfo to itself contain ":" characters
StringBuilder extendedInfo = new StringBuilder();
for (int i = 2; i < tokens.length - 1; i++) {
if (i > 2) {
extendedInfo.append(":");
}
extendedInfo.append(tokens[i]);
}
String sha1Hex = tokens[tokens.length - 1];
// Verification
String content = creationTime + ":" + pseudoRandomNumber + ":" + extendedInfo.toString();
String expectedSha512Hex = Sha512DigestUtils.shaHex(content + ":" + serverSecret);
Assert.isTrue(Utf8.isEqual(expectedSha512Hex, sha1Hex), "Key verification failure");
return new DefaultToken(key, creationTime, extendedInfo.toString());
}View on GitHub (pinned to 96852e8860)