apereo/cas · warning
Throttled submission
Error message
Throttled submission [{}] remains throttled; submission expires at [{}] What it means
AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.exceedsThreshold checks the in-memory throttle store for an existing entry for the failure key. If a record exists and has not expired, the request is still considered throttled; this warning reports the key and expiration time, and the request attribute ThrottledSubmission is set before the request is rejected.
Solutions
- Wait until the reported expiration time passes; the in-memory store expires entries automatically.
- Restart CAS or clear the in-memory throttle store to immediately lift the block (dev/testing).
- Adjust cas.authn.throttle.failure.threshold-rate / range to a less aggressive policy.
- Allow-list or bypass the offending trusted client/IP if it is a legitimate automated caller.
Example fix
// before (too aggressive) cas.authn.throttle.failure.threshold=3 cas.authn.throttle.failure.range-seconds=3600 // after cas.authn.throttle.failure.threshold=10 cas.authn.throttle.failure.range-seconds=60
Defensive patterns
Strategy: fallback
Validate before calling
// client-side: back off until expiration before retrying
if (response.containsHeader("Retry-After")) {
long waitSecs = Long.parseLong(response.getFirstHeader("Retry-After").getValue());
Thread.sleep(TimeUnit.SECONDS.toMillis(waitSecs));
} Try / catch
// treat HTTP 429 / throttled response as transient
try {
return authenticate(credentials);
} catch (ThrottledSubmissionException e) {
LOGGER.warn("Throttled until {}", e.getExpiration());
return AuthenticationResult.throttled(e.getExpiration());
} Prevention
- Implement exponential backoff in clients after failed authentication.
- Alert on repeated 'remains throttled' warnings per IP to detect attacks.
- Size threshold/range settings against realistic user failure rates.
When it happens
Trigger: A client whose identifier (username/IP) was previously recorded as a failed authentication attempt submits again before the throttle interval defined by the failure threshold rate has elapsed.
Common situations: User repeatedly failing password attempts and being locked out; an automated client (script, monitoring probe) hammering the login endpoint from one IP; stale throttle entries after clock/config changes to threshold rate.
Related errors
- Throttling submission from
- Authentication throttling rate
- Validation attempt for principal is throttled
- Access Denied for user
- Authentication handler is disabled
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/b159fb2e3008cfb8.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-throttle-core/src/main/java/org/apereo/cas/throttle/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java:65
.clientIpAddress(ClientInfoHolder.getClientInfo().getClientIpAddress())
.build();
LOGGER.debug("Recording submission failure entry [{}]", submission);
getConfigurationContext().getThrottledSubmissionStore().put(submission);
throttledSubmissionReceivers.forEach(Unchecked.consumer(receiver -> receiver.receive(submission)));
LOGGER.info("Recorded submission failure [{}] for [{}]", submission, key);
}
@Override
public boolean exceedsThreshold(final HttpServletRequest request) {
val key = constructKey(request);
LOGGER.trace("Throttling threshold key is [{}] with calculated threshold [{}]", key, getThresholdRate());
val store = getConfigurationContext().getThrottledSubmissionStore();
if (store.contains(key)) {
val submission = store.get(key);
LOGGER.trace("Found existing throttled submission [{}] for key [{}]", submission, key);
if (!Objects.requireNonNull(submission).hasExpiredAlready()) {
LOGGER.warn("Throttled submission [{}] remains throttled; submission expires at [{}]", key, submission.getExpiration());
request.setAttribute(ThrottledSubmission.class.getSimpleName(), submission);
return true;
}
}
if (store.exceedsThreshold(key, getThresholdRate())) {
val submission = store.get(key);
request.setAttribute(ThrottledSubmission.class.getSimpleName(), submission);
return true;
}
return false;
}
@Override
public void release() {
try {
LOGGER.debug("Beginning audit cleanup...");
getConfigurationContext().getThrottledSubmissionStore().release(getThresholdRate());
} finally {View on GitHub (pinned to e7288fc434)