apereo/cas · warning

No expiration policy was found for ticket state

Error message

No expiration policy was found for ticket state [{}] to calculate time-to-live. Consider configuring a predicate that delegates to an expiration policy.

What it means

Same resolution failure as the isExpired variant, but on getTimeToLive(): the delegating policy could not find an inner policy named for the ticket state, so it logs this warning and returns the superclass's default TTL. This means TTL numbers reported for such tickets come from the fallback policy, not a per-state policy.

Solutions

  1. Add a DEFAULT-named policy via addPolicy(POLICY_NAME_DEFAULT, ...) so TTL resolution always succeeds.
  2. Fix the predicate in getExpirationPolicyNameFor() so it maps every expected ticket state to a registered policy name.
  3. Verify registered policy names match exactly (case-sensitive) what the predicate returns.
  4. Check that the policies map was not emptied by deserialization before use.

Example fix

// before
return super.getTimeToLive(ticketState); // reached because no policy matched
// after
val delegating = new PrincipalAttributeDelegatingExpirationPolicy(...);
delegating.addPolicy(BaseDelegatingExpirationPolicy.POLICY_NAME_DEFAULT, TimeoutExpirationPolicy.builder().build());
Defensive patterns

Strategy: validation

Validate before calling

val name = policy.getExpirationPolicyNameFor(state); if (name == null || !policy.getPolicies().containsKey(name)) { /* fix predicate/config before deployment */ }

Prevention

When it happens

Trigger: Calling getTimeToLive(ticket) (or code that computes remaining lifetime) on a BaseDelegatingExpirationPolicy whose getExpirationPolicyNameFor() returns a blank or unregistered name for that ticket.

Common situations: Ticket state whose authentication attributes do not satisfy any configured predicate; policies map missing a DEFAULT entry so TTL reporting is misleading; custom delegating policy subclass returning null names.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/df7d51bf3491585f. Report an issue: GitHub.

Appendix: source

Thrown at core/cas-server-core-tickets-api/src/main/java/org/apereo/cas/ticket/expiration/BaseDelegatingExpirationPolicy.java:96

    @Override
    public boolean isExpired(final TicketGrantingTicketAwareTicket ticketState) {
        val match = getExpirationPolicyFor(ticketState);
        if (match.isEmpty()) {
            LOGGER.warn("No expiration policy was found for ticket state [{}]. "
                + "Consider configuring a predicate that delegates to an expiration policy.", ticketState);
            return super.isExpired(ticketState);
        }
        val policy = match.get();
        LOGGER.trace("Activating expiration policy [{}] for ticket [{}]", policy.getName(), ticketState);
        return policy.isExpired(ticketState);
    }

    @Override
    public Long getTimeToLive(final Ticket ticketState) {
        val match = getExpirationPolicyFor((AuthenticationAwareTicket) ticketState);
        if (match.isEmpty()) {
            LOGGER.warn("No expiration policy was found for ticket state [{}] to calculate time-to-live. "
                + "Consider configuring a predicate that delegates to an expiration policy.", ticketState);
            return super.getTimeToLive(ticketState);
        }
        val policy = match.get();
        LOGGER.trace("Getting TTL from policy [{}] for ticket [{}]", policy.getName(), ticketState);
        return policy.getTimeToLive(ticketState);
    }

    @JsonIgnore
    @Override
    public Long getTimeToLive() {
        return this.policies.get(POLICY_NAME_DEFAULT).getTimeToLive();
    }

    protected Optional<ExpirationPolicy> getExpirationPolicyFor(final AuthenticationAwareTicket ticketState) {
        val name = getExpirationPolicyNameFor(ticketState);
        LOGGER.trace("Received expiration policy name [{}] to activate", name);
        if (StringUtils.isNotBlank(name) && policies.containsKey(name)) {

View on GitHub (pinned to e7288fc434)