apereo/cas · warning
Ticket passed is null and cannot be decoded
Error message
Ticket passed is null and cannot be decoded
What it means
AbstractTicketRegistry.decodeTicket() receives a null ticket when the registry has a cipher executor (encryption/signing) enabled. Instead of throwing, it logs a warning and returns null so callers must handle a null result. It indicates that a null was passed into the decode path, usually because the ticket lookup upstream returned nothing.
Solutions
- Check where the ticket ID originates (cookie, parameter) and handle the null return instead of assuming a non-null ticket
- Verify the ticket still exists in the registry (e.g. ticketRegistry.getTicket(id)) before further processing
- If nulls are expected to be common, confirm decodeTicket's null contract is handled at every call site rather than treating the warn as a bug
- If it should never be null, debug why the upstream lookup produced a null ticket value (e.g. broken cookie extraction)
Example fix
// before
Ticket t = ticketRegistry.getTicket(ticketId);
String decoded = ticketRegistry.decodeTicket(t.getValue()); // NPE if t is null
// after
Ticket t = ticketRegistry.getTicket(ticketId);
if (t == null) {
LOGGER.warn("No ticket found for [{}]", ticketId);
return null;
}
String decoded = ticketRegistry.decodeTicket(t.getValue()); Defensive patterns
Strategy: type-guard
Validate before calling
if (ticketId == null || ticketId.isBlank()) { throw new IllegalArgumentException("ticket id required"); } Type guard
Ticket t = ticketRegistry.getTicket(ticketId);
if (t == null || ticketRegistry.decodeTicket(t.getId()) == null) {
// treat as no-ticket / expired
} Prevention
- Always null-check the result of ticket lookup before decoding
- Handle expired/consumed tickets as a normal control-flow case, not an exception
- In clustered deployments, account for replication lag when a ticket ID is present on one node only
When it happens
Trigger: Calling decodeTicket(null) directly; or any registry path (getTicket, deleteTicket, etc.) that passes a null ticket string/object into decodeTicket while isCipherExecutorEnabled() is true.
Common situations: Requesting a ticket ID that no longer exists (expired or single-use and already consumed); client passing an empty/missing TGT cookie; replication lag between clustered ticket registries returning null.
Related errors
- Final ticket id [ ] length [ ] exceeds [ ] characters
- Unable to locate ticket map for ticket metadata
- Unable to accept cookie for authentication
- Pattern cannot be null/blank
- Service ticket [ ] does not exist.
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/719280615a2d2444.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-tickets-api/src/main/java/org/apereo/cas/ticket/registry/AbstractTicketRegistry.java:346
}
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);
val ticket = decodeAndDeserialize(encodedTicket.getEncodedTicket());
LOGGER.debug("Decoded ticket to [{}]", ticket);
return ticket;
}
protected Ticket decodeAndDeserialize(final byte[] encodedTicket) {
return SerializationUtils.decodeAndDeserializeObject(encodedTicket, this.cipherExecutor, Ticket.class);
}
protected Collection<Ticket> decodeTickets(final Collection<Ticket> items) {View on GitHub (pinned to e7288fc434)