apereo/cas · warning

No expiration policy could be found by the name

Error message

No expiration policy could be found by the name [{}] for ticket state [{}]

What it means

This is the low-level lookup inside BaseDelegatingExpirationPolicy.getExpirationPolicyFor(): the name produced for a ticket state is blank or absent from the `policies` map, so it logs a warning and returns Optional.empty(). Callers (isExpired, getTimeToLive, toMaximumExpirationTime) then fall back to default behavior. It is a configuration/naming mismatch signal, not a thrown exception.

Solutions

  1. Log/print getExpirationPolicyNameFor(state) and the policies keySet; align the predicate's returned name with a registered key.
  2. Register the missing policy under the exact name, or add a DEFAULT policy as catch-all.
  3. Fix the custom predicate to return POLICY_NAME_DEFAULT instead of null/blank when nothing matches.
  4. If policies were lost in serialization, re-add them after deserialization or persist the full policy object.

Example fix

// before
String name = attributes.get("policyName"); // null for most users
// after
String name = attributes.getOrDefault("policyName", BaseDelegatingExpirationPolicy.POLICY_NAME_DEFAULT);
Defensive patterns

Strategy: validation

Validate before calling

val keys = policy.getPolicies().keySet(); val chosen = policy.getExpirationPolicyNameFor(ticket); log.debug("chosen={} keys={}", chosen, keys); if (chosen == null || !keys.contains(chosen)) { registerMissingPolicy(chosen); }

Prevention

When it happens

Trigger: getExpirationPolicyFor is invoked during any ticket expiration check when getExpirationPolicyNameFor(ticketState) returns a name that is blank or not a key of the policies map (names compared exactly via policies.containsKey).

Common situations: Typos between the name passed to addPolicy(name, policy) and the name the predicate produces; policy map reset by JSON deserialization; custom subclass returning null when no predicate matches.

Related errors


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

Appendix: source

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

        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)) {
            val policy = policies.get(name);
            LOGGER.trace("Located expiration policy [{}] by name [{}]", policy, name);
            return Optional.of(policy);
        }
        LOGGER.warn("No expiration policy could be found by the name [{}] for ticket state [{}]", name, ticketState);
        return Optional.empty();
    }

    protected abstract String getExpirationPolicyNameFor(AuthenticationAwareTicket ticketState);

    @Override
    public ZonedDateTime toMaximumExpirationTime(final Ticket ticketState) {
        val result = getExpirationPolicyFor((AuthenticationAwareTicket) ticketState);
        return result.map(policy -> policy.toMaximumExpirationTime(ticketState))
            .orElseGet(() -> ZonedDateTime.now(Clock.systemUTC()));
    }
}

View on GitHub (pinned to e7288fc434)