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
- Log/print getExpirationPolicyNameFor(state) and the policies keySet; align the predicate's returned name with a registered key.
- Register the missing policy under the exact name, or add a DEFAULT policy as catch-all.
- Fix the custom predicate to return POLICY_NAME_DEFAULT instead of null/blank when nothing matches.
- 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
- Exact-match names: policies.containsKey is case-sensitive — use constants, not literals
- Return POLICY_NAME_DEFAULT from custom getExpirationPolicyNameFor implementations instead of null
- Re-check policy registration after any change to ticket serialization format
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
- No expiration policy was found for ticket state
- No expiration policy was found for ticket state
- Expired [ ]: number of seconds since ticket usage time [ ]…
- 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/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)