SonarSource/sonarqube · error · IllegalArgumentException
Tokens expiring after %s are not allowed. Please use a valid
Error message
Tokens expiring after %s are not allowed. Please use a valid expiration date.
What it means
compareExpirationDateToMaxAllowedLifetime throws this IllegalArgumentException when the requested token expiration_date is strictly after the maximum allowed by the server's max token lifetime setting. The message includes the latest permitted date (ISO format), making it clear which window is acceptable.
Source
Thrown at server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateActionValidation.java:104
}
static void validateMaxExpirationDate(MaxTokenLifetimeOption maxTokenLifetime, LocalDate expirationDate) {
maxTokenLifetime.getDays()
.ifPresent(days -> compareExpirationDateToMaxAllowedLifetime(expirationDate, LocalDate.now(ZoneOffset.UTC).plusDays(days)));
}
static void validateMaxExpirationDate(MaxTokenLifetimeOption maxTokenLifetime) {
maxTokenLifetime.getDays()
.ifPresent(days -> {
throw new IllegalArgumentException(
String.format("Tokens expiring after %s are not allowed. Please use an expiration date.",
LocalDate.now(ZoneOffset.UTC).plusDays(days).format(DateTimeFormatter.ISO_DATE)));
});
}
static void compareExpirationDateToMaxAllowedLifetime(LocalDate expirationDate, LocalDate maxExpirationDate) {
if (expirationDate.isAfter(maxExpirationDate)) {
throw new IllegalArgumentException(
String.format("Tokens expiring after %s are not allowed. Please use a valid expiration date.",
maxExpirationDate.format(DateTimeFormatter.ISO_DATE)));
}
}
static void validateMinExpirationDate(LocalDate localDate) {
if (localDate.isBefore(LocalDate.now(ZoneOffset.UTC).plusDays(1))) {
throw new IllegalArgumentException(
String.format("The minimum value for parameter %s is %s.", PARAM_EXPIRATION_DATE, LocalDate.now(ZoneOffset.UTC).plusDays(1).format(DateTimeFormatter.ISO_DATE)));
}
}
static void validateParametersCombination(UserTokenSupport userTokenSupport, DbSession dbSession, Request request, TokenType tokenType) {
if (PROJECT_ANALYSIS_TOKEN.equals(tokenType)) {
validateProjectAnalysisParameters(userTokenSupport, dbSession, request);
} else if (GLOBAL_ANALYSIS_TOKEN.equals(tokenType)) {
validateGlobalAnalysisParameters(userTokenSupport, request);
}View on GitHub (pinned to 184c821202)
Solutions
- Cap the expiration at the date shown in the message (maxExpirationDate); compute it as today UTC plus the configured max days.
- Set expiration_date to that boundary or earlier, and add token rotation to automation.
- If the requested duration is a genuine business need, have a server admin raise sonar.auth.token.max-allowed-lifetime.
- Dynamically read/adapt: compute expiry in the script from the allowed lifetime instead of hardcoding dates.
Example fix
// before: hardcoded one-year expiry curl -su "$TOKEN:" -X POST 'https://sonar/api/user_tokens/generate?name=ci&expiration_date=2027-09-09' // after: clamp to policy (e.g. 90 days) EXP=$(date -u -d '+90 days' +%F) curl -su "$TOKEN:" -X POST "https://sonar/api/user_tokens/generate?name=ci&expiration_date=$EXP"
Defensive patterns
Strategy: validation
Validate before calling
function clampToPolicy(expiryIso, maxDays) {
const max = new Date(Date.now() + maxDays * 86400000).toISOString().slice(0, 10);
return expiryIso > max ? max : expiryIso;
}
const safeExp = clampToPolicy(requestedExp, maxLifetimeDays); Type guard
function isWithinWindow(e, maxDays) {
if (typeof e !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(e)) return false;
return e <= new Date(Date.now() + maxDays * 86400000).toISOString().slice(0, 10);
} Try / catch
try {
await generateToken(name, exp);
} catch (e) {
if (e.status === 400 && /use a valid expiration date/.test(e.message)) {
const max = e.message.match(/after (\d{4}-\d{2}-\d{2})/)?.[1];
return generateToken(name, max); // retry at the boundary
}
throw e;
} Prevention
- Compute expirations relative to today UTC, never hardcode future dates
- Clamp requested expiry to now + maxLifetimeDays before calling
- Re-validate token durations after any policy change or server migration
- Include rotation jobs so clamped lifetimes stay sufficient
When it happens
Trigger: POST api/user_tokens/generate with expiration_date later than LocalDate.now(UTC) + maxLifetimeDays, e.g. requesting 1 year when policy allows 90 days.
Common situations: Scripts hardcoding '+1 year' expirations; policy tightened from unlimited to a capped lifetime while automation kept old values; server migration to an org with stricter token policies; off-by-boundary requests (day after cutoff).
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Supplied date format for parameter %s is wrong. Please suppl
- Tokens expiring after %s are not allowed. Please use an expi
- Command-line argument must start with -D, for example -Dsona
- Bad format of JDBC URL: %s
- Unknown severity: %s
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/33a740dccd9c10ae.
Report an issue: GitHub.