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

  1. Understand the ticket is intentionally rejected: obtain a new service ticket and validate only once.
  2. Lower cas.ticket.<type>.time-in-between-uses-in-seconds (or set to 0) if legitimate fast reuse is expected in your flow.
  3. Synchronize clocks (NTP) across CAS cluster nodes to avoid spurious near-zero margins.
  4. 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

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


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;
    }

    @JsonIgnore

View on GitHub (pinned to e7288fc434)