apereo/cas · warning
Found removable encoded ticket
Error message
Found removable encoded ticket [{}] yet cipher operations are disabled. What it means
AbstractTicketRegistry.decodeTicket detects an EncodedTicket in the registry while the registry's ticket cipher executor is disabled (isCipherExecutorEnabled() == false). Because the ticket cannot be decoded, it is treated as garbage: it is deleted and null is returned (caller sees 'ticket not found'). The warning points at an encryption configuration mismatch over the ticket registry's lifetime.
Solutions
- Flush/clear the ticket registry when toggling cas.ticket.crypto.enabled so no stale EncodedTicket entries remain (users simply log in again).
- Keep ticket crypto configuration consistent across ALL nodes in the cluster during rolling upgrades; enable/disable everywhere at once behind a maintenance window.
- Re-enable the cipher executor if disabling was unintentional, restoring the previous crypto (signing/encryption) keys so existing encoded tickets decode.
- If the warnings are transient after a migration, they are self-healing: each warning deletes one stale ticket; monitor until it stops.
Example fix
// before (rolling restart with mixed config) node1: cas.ticket.crypto.enabled=false node2: cas.ticket.crypto.enabled=true // after: uniform config + registry flush before restart all nodes: cas.ticket.crypto.enabled=false # then: FLUSHDB the redis ticket registry (or equivalent) before serving traffic
Defensive patterns
Strategy: fallback
Validate before calling
if (!registry.isCipherExecutorEnabled()) { /* ensure registry was flushed of EncodedTicket entries before switching crypto off */ } Type guard
boolean needsDecodeCheck = ticket instanceof EncodedTicket && !registry.isCipherExecutorEnabled();
Try / catch
// decodeTicket returns null for stale encoded tickets; caller must handle null
Ticket t = ticketRegistry.getTicket(id);
if (t == null) { /* force re-login */ } Prevention
- Keep cas.ticket.crypto.enabled identical on all nodes; never mix enabled/disabled nodes
- Flush the shared ticket registry when toggling ticket encryption or rotating keys
- Plan crypto changes behind a maintenance window and monitor for this warn during rollout
When it happens
Trigger: cas.ticket.crypto.enabled was switched from true to false (or the crypto signing/encryption keys/config changed) while encoded tickets from the previous configuration still exist in the shared registry; decodeTicket then deletes each such ticket on read.
Common situations: Rolling upgrades where some nodes encrypt tickets and others have crypto disabled; reverting a ticket-encryption rollout without flushing the ticket store; shared Redis/Memcached/JDBC registry containing pre-change encoded tickets.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- InvalidTicketException
- warn(logger, getMessage(throwable), throwable)
- Could not grant service ticket
- No authentication found for ticket
- Principal attribute [
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/8d04c4fe45fcc165.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-tickets-api/src/main/java/org/apereo/cas/ticket/registry/AbstractTicketRegistry.java:336
}
protected @Nullable Ticket encodeTicket(final Ticket ticket) throws Exception {
if (!isCipherExecutorEnabled()) {
LOGGER.trace(TICKET_ENCRYPTION_LOG_MESSAGE);
return ticket;
}
if (ticket == null) {
LOGGER.debug("Ticket passed is null and cannot be encoded");
return null;
}
val encodedTicket = createEncodedTicket(ticket);
LOGGER.debug("Created encoded ticket [{}]", encodedTicket);
return encodedTicket;
}
protected @Nullable Ticket decodeTicket(final Ticket ticketToProcess) {
if (ticketToProcess instanceof EncodedTicket && !isCipherExecutorEnabled()) {
LOGGER.warn("Found removable encoded ticket [{}] yet cipher operations are disabled.", ticketToProcess.getId());
FunctionUtils.doUnchecked(_ -> deleteTicket(ticketToProcess));
return null;
}
if (!isCipherExecutorEnabled()) {
LOGGER.trace(TICKET_ENCRYPTION_LOG_MESSAGE);
return ticketToProcess;
}
if (ticketToProcess == null) {
LOGGER.warn("Ticket passed is null and cannot be decoded");
return null;
}
if (!(ticketToProcess instanceof final EncodedTicket encodedTicket)) {
LOGGER.debug("Ticket passed is not an encoded ticket: [{}], no decoding is necessary.",
ticketToProcess.getClass().getSimpleName());
return ticketToProcess;
}
LOGGER.debug("Attempting to decode [{}]", ticketToProcess);View on GitHub (pinned to e7288fc434)