apereo/cas · warning

No expiration policy was found for ticket state

Error message

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

What it means

BaseDelegatingExpirationPolicy routes isExpired() checks to a named inner expiration policy chosen by getExpirationPolicyNameFor(ticketState). If no policy is registered under that name (or the name is blank), it logs this warning and falls back to the superclass default behavior rather than throwing. It signals misconfiguration of the delegating policy's predicate/policy map.

Solutions

  1. Register a policy under BaseDelegatingExpirationPolicy.POLICY_NAME_DEFAULT ("DEFAULT") via addPolicy(...) so unmatched ticket states have a fallback.
  2. Inspect getExpirationPolicyNameFor() output for a ticket state that matched nothing and fix the predicate/attribute configuration so it returns a registered name.
  3. Verify the `policies` map contents (they may have been lost via deserialization/JSON binding with Nulls.AS_EMPTY).
  4. If the fallback is acceptable, silence by ensuring the superclass expiration policy behavior is what you intend; otherwise fix configuration.

Example fix

// before
val policy = new PrincipalAttributeDelegatingExpirationPolicy(...); // only named policies added, no default
// after
policy.addPolicy("DEFAULT", TimeoutExpirationPolicy.builder().build()); // fallback for unmatched states
Defensive patterns

Strategy: validation

Validate before calling

assert delegatingPolicy.getPolicies().containsKey("DEFAULT") : "Delegating expiration policy has no DEFAULT fallback policy";

Prevention

When it happens

Trigger: Calling isExpired(ticket) on a delegating expiration policy whose `policies` map lacks an entry for the name returned by getExpirationPolicyNameFor() — e.g. a predicate-based policy builder produced no matching policy for the ticket's authentication/principal attributes.

Common situations: Custom principal-attribute-based expiration policies where the attribute used to pick a policy is missing on the ticket's authentication; policies registered only under non-DEFAULT names while a ticket matches none of the predicates; upgrade where policy registration names changed.

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/8d5bf59bffddf823. Report an issue: GitHub.

Appendix: source

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

    /**
     * Add policy.
     *
     * @param name   the name
     * @param policy the policy
     * @return the base delegating expiration policy
     */
    @CanIgnoreReturnValue
    public BaseDelegatingExpirationPolicy addPolicy(final String name, final ExpirationPolicy policy) {
        LOGGER.trace("Adding expiration policy [{}] with name [{}]", policy, name);
        this.policies.put(name, policy);
        return this;
    }

    @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);

View on GitHub (pinned to e7288fc434)