apereo/cas · warning
Expired [ ]: number of seconds since ticket usage time [ ]…
Error message
Expired [{}]: number of seconds since ticket usage time [{}] is less than or equal to time in between uses in seconds [{}] What it means
ThrottledUseAndTimeoutExpirationPolicy.isExpired() returns true (ticket expired) when the ticket is being used sooner than `timeInBetweenUsesInSeconds` allows — i.e. the ticket is being used 'too fast', which this policy treats as invalid. This prevents rapid automated replay of tickets while still allowing the ticket to live up to timeToKillInSeconds. The message wording ('Expired ... is less than or equal to time in between uses') is the warn log emitted in that branch.
Solutions
- Understand the ticket is intentionally rejected: obtain a new service ticket and validate only once.
- Lower cas.ticket.<type>.time-in-between-uses-in-seconds (or set to 0) if legitimate fast reuse is expected in your flow.
- Synchronize clocks (NTP) across CAS cluster nodes to avoid spurious near-zero margins.
- If your application needs multiple validations, use a different expiration policy (e.g. TimeoutExpirationPolicy) instead of the throttled one.
Example fix
// before (application.properties) cas.ticket.st.time-in-between-uses-in-seconds=5 // after cas.ticket.st.time-in-between-uses-in-seconds=0 # or validate each ST exactly once and discard
Defensive patterns
Strategy: validation
Validate before calling
// ensure legit reuse interval is larger than the throttle window cas.ticket.st.time-in-between-uses-in-seconds=0 # or > max expected client retry delay
Prevention
- Validate each service ticket exactly once and discard it
- Set time-in-between-uses-in-seconds to 0 unless you deliberately want throttling
- Run NTP on all CAS nodes so margin calculations are stable
When it happens
Trigger: Validating a ticket whose seconds-since-last-use (margin) is > 0 and <= timeInBetweenUsesInSeconds. E.g. cas.ticket.st.timeToKillInSeconds=10 but timeInBetweenUsesInSeconds also small and the client re-validates within that window.
Common situations: Clients validating the same ticket twice in quick succession (the second call); misconfigured `cas.ticket.*.time-in-between-uses-in-seconds` set too high relative to legitimate client behavior; clock skew between CAS nodes making margin compute near zero for a just-used ticket.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- No expiration policy was found for ticket state
- No expiration policy was found for ticket state
- No expiration policy could be found by the name
- Primary ticket-granting ticket expiration policy is set to…
- Ticket-granting ticket expiration policy is set to ALWAYS…
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/7c64a66f2862bdc8.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-tickets-api/src/main/java/org/apereo/cas/ticket/expiration/ThrottledUseAndTimeoutExpirationPolicy.java:71
LOGGER.trace("Current time is [{}]. Ticket last used time is [{}]", currentTime, lastTimeUsed);
val margin = Duration.between(lastTimeUsed, currentTime).toSeconds();
LOGGER.trace("The duration in seconds between current time and last used time is [{}]", margin);
if (ticketState.getCountOfUses() == 0 && margin < this.timeToKillInSeconds) {
LOGGER.debug("Valid [{}]: Usage count is zero and number of seconds since ticket usage time [{}] is less than [{}]",
ticketState, margin, this.timeToKillInSeconds);
return super.isExpired(ticketState);
}
if (margin >= this.timeToKillInSeconds) {
LOGGER.debug("Expired [{}]: number of seconds since ticket usage time [{}] is greater than or equal to [{}]",
ticketState, margin, this.timeToKillInSeconds);
return true;
}
if (margin > 0 && margin <= this.timeInBetweenUsesInSeconds) {
LOGGER.warn("Expired [{}]: number of seconds since ticket usage time [{}] is less than or equal to time in between uses in seconds [{}]",
ticketState, margin, this.timeInBetweenUsesInSeconds);
return true;
}
return super.isExpired(ticketState);
}
@Override
public Long getTimeToLive() {
return this.timeToKillInSeconds;
}
@Override
public Long getTimeToIdle() {
return this.timeInBetweenUsesInSeconds;
}
@JsonIgnoreView on GitHub (pinned to e7288fc434)