languagetool-org/languagetool · error · TooManyRequestsException

Client request limit of requests per seconds exceeded

Error message

Client request limit of  requests per  seconds exceeded

What it means

HTTP 429 error thrown when requests sharing the same client fingerprint exceed the per-client request limit within the rate-limit period. This complements the per-IP limit and applies when ipFingerprintFactor and requestLimit are positive.

Source

Thrown at languagetool-server/src/main/java/org/languagetool/server/RequestLimiter.java:219

        float modeFactor = event.mode == JLanguageTool.Mode.TEXTLEVEL_ONLY ? 0.1f : 1f;
        requestsByIp++;
        requestSizeByIp += event.getSizeInBytes() * modeFactor;
        if (whitelistedUser) {
          if (whitelistLimit <= 0 || requestsByIp < whitelistLimit) {
            continue;
          } else {
            String msg = "limit: " + ipRequestLimit + " / " + requestLimitPeriodInSeconds + ", requests: "  + requestsByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
            throw new TooManyRequestsException("Whitelist request limit of " + whitelistLimit + " requests per " +
              requestLimitPeriodInSeconds + " seconds exceeded");
          }
        }
        if (event.fingerprint.equals(fingerprint)) {
          requestsByFingerprint++;
          requestSizeByFingerprint += event.getSizeInBytes() * modeFactor;
        }
        if (ipFingerprintFactor > 0 && requestLimit > 0 && requestsByFingerprint > requestLimit) {
          String msg = "limit: " + requestLimit + " / " + requestLimitPeriodInSeconds + ", requests: "  + requestsByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
          throw new TooManyRequestsException("Client request limit of " + requestLimit + " requests per " +
            requestLimitPeriodInSeconds + " seconds exceeded"); }
        if (requestLimit > 0 && requestsByIp > ipRequestLimit) {
          String msg = "limit: " + ipRequestLimit + " / " + requestLimitPeriodInSeconds + ", requests: "  + requestsByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
          throw new TooManyRequestsException("IP request limit of " + ipRequestLimit + " requests per " +
            requestLimitPeriodInSeconds + " seconds exceeded");
        }
        if (event.mode == JLanguageTool.Mode.TEXTLEVEL_ONLY) {
          if (ipFingerprintFactor > 0 && requestLimitInBytes > 0 && requestSizeByFingerprint > requestLimitInBytes) {
            String msg = "limit in Mode.TEXTLEVEL_ONLY: " + requestLimitInBytes + " / " + requestLimitPeriodInSeconds + ", request size: "  + requestSizeByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
            throw new TooManyRequestsException("Client request size limit of " + requestLimitInBytes + " bytes per " +
              requestLimitPeriodInSeconds + " seconds exceeded in text-level checks");
          }
          if (requestLimitInBytes > 0 && requestSizeByIp > ipRequestLimitInBytes) {
            String msg = "limit in Mode.TEXTLEVEL_ONLY: " + ipRequestLimitInBytes + " / " + requestLimitPeriodInSeconds + ", request size: "  + requestSizeByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
            throw new TooManyRequestsException("IP request size limit of " + ipRequestLimitInBytes + " bytes per " +
              requestLimitPeriodInSeconds + " seconds exceeded in text-level checks");
          }
        } else {

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Add client-side throttling: queue requests and keep under requestLimit per period
  2. Increase the server's client requestLimit configuration
  3. Stagger retries with exponential backoff and jitter instead of tight retry loops
Defensive patterns

Strategy: retry

Validate before calling

class Limiter {
  constructor(max, periodMs) { this.max = max; this.periodMs = periodMs; this.hits = []; }
  async acquire() {
    const now = Date.now();
    this.hits = this.hits.filter(t => now - t < this.periodMs);
    if (this.hits.length >= this.max) throw new Error('Client rate limit would be exceeded');
    this.hits.push(now);
  }
}

Try / catch

try {
  return await check(text);
} catch (e) {
  if (e.status === 429 && /Client request limit/.test(e.message)) {
    await backoff(attempt); // exponential backoff with jitter
    return check(text);
  }
  throw e;
}

Prevention

When it happens

Trigger: checkLimit detects requestsByFingerprint > requestLimit for one fingerprint within requestLimitPeriodInSeconds.

Common situations: A single application instance (same fingerprint, e.g. same client token) sending high-volume traffic, parallel worker threads without throttling, or aggressive retry loops amplifying request counts.

Related errors


AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06). Data as JSON: /api/errors/83fa6b966207b1d3. Report an issue: GitHub.