apereo/cas · error · IllegalArgumentException

No transaction found for

Error message

No transaction found for [%s]

What it means

OidcVerifiableCredentialDefaultOfferService.fetch looks up a TransientSessionTicket by transactionId in the ticket registry and throws IllegalArgumentException when no such transaction ticket exists. It guards the credential-offer flow against unknown, expired, or consumed transaction IDs.

Solutions

  1. Restart the credential-offer flow to obtain a fresh transactionId
  2. Check the TransientSessionTicket timeout configuration and raise it if users legitimately take longer
  3. Verify all CAS nodes share the same ticket registry (Redis/JDBC/Hazelcast) so the ticket is visible cluster-wide

Example fix

// before
OidcVerifiableCredentialOffer offer = offerService.fetch(oldTransactionId);
// after
String txId = startNewCredentialOfferTransaction();
OidcVerifiableCredentialOffer offer = offerService.fetch(txId); // fresh, unexpired transaction
Defensive patterns

Strategy: try-catch

Validate before calling

TransientSessionTicket t = (TransientSessionTicket) transactionService.fetch(txId);
if (t == null) { restartOfferFlow(); }

Try / catch

try { return offerService.fetch(txId); }
catch (IllegalArgumentException e) { // retry with a new transaction
  String newTx = startNewTransaction(); return offerService.fetch(newTx); }

Prevention

When it happens

Trigger: Calling fetch(transactionId) with an ID that was never issued, one whose TransientSessionTicket expired (transient tickets have a short TTL), one already consumed/removed, or a ticket registry that lost the entry (restart, non-shared registry across nodes).

Common situations: User bookmarks/refreshes an offer URL after the transient ticket expired; load-balanced CAS nodes without a shared ticket registry; client reusing an old transactionId from a previous session.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/offer/OidcVerifiableCredentialDefaultOfferService.java:33

 * @since 8.0.0
 */
@RequiredArgsConstructor
@Slf4j
public class OidcVerifiableCredentialDefaultOfferService implements OidcVerifiableCredentialOfferService {
    private final OidcConfigurationContext configurationContext;
    private final OidcVerifiableCredentialTransactionService transactionService;

    @Override
    public OidcVerifiableCredentialOffer create(final String clientId, final String principalId, final List<String> credentialConfigurationIds) {
        val transaction = (TransientSessionTicket) transactionService.issue(clientId, principalId, credentialConfigurationIds);
        return buildCredentialOffer(Objects.requireNonNull(transaction));
    }

    @Override
    public OidcVerifiableCredentialOffer fetch(final String transactionId) {
        val transaction = (TransientSessionTicket) transactionService.fetch(transactionId);
        if (transaction == null) {
            throw new IllegalArgumentException(String.format("No transaction found for [%s]", transactionId));
        }
        return buildCredentialOffer(transaction);
    }

    private @NonNull OidcVerifiableCredentialOffer buildCredentialOffer(final TransientSessionTicket transaction) {
        val credentialConfigurationIds = transaction.getProperty("credentialConfigurationIds", List.class);
        val issuer = configurationContext.getCasProperties().getAuthn().getOidc().getCore().getIssuer();

        val grant = new OidcVerifiableCredentialOffer.Grants.PreAuthorizedCodeGrant();
        grant.setTransactionCode(
            OidcVerifiableCredentialOffer.Grants.TransactionCode
                .builder()
                .value(Objects.requireNonNull(transaction).getId())
                .length(Objects.requireNonNull(transaction).getId().length())
                .build()
        );
        grant.setPreAuthorizedCode(transaction.getPropertyAsString("preAuthorizedCode"));
        grant.setIssuerState(transaction.getPropertyAsString("issuerState"));

View on GitHub (pinned to e7288fc434)