{"record":{"id":"83fa6b966207b1d3","repo":"languagetool-org/languagetool","slug":"client-request-limit-of-requests-per-seconds-exc","errorCode":null,"errorMessage":"Client request limit of  requests per  seconds exceeded","messagePattern":"Client request limit of  requests per  seconds exceeded","errorType":"http","errorClass":"TooManyRequestsException","httpStatus":429,"severity":"error","filePath":"languagetool-server/src/main/java/org/languagetool/server/RequestLimiter.java","lineNumber":219,"sourceCode":"        float modeFactor = event.mode == JLanguageTool.Mode.TEXTLEVEL_ONLY ? 0.1f : 1f;\n        requestsByIp++;\n        requestSizeByIp += event.getSizeInBytes() * modeFactor;\n        if (whitelistedUser) {\n          if (whitelistLimit <= 0 || requestsByIp < whitelistLimit) {\n            continue;\n          } else {\n            String msg = \"limit: \" + ipRequestLimit + \" / \" + requestLimitPeriodInSeconds + \", requests: \"  + requestsByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n            throw new TooManyRequestsException(\"Whitelist request limit of \" + whitelistLimit + \" requests per \" +\n              requestLimitPeriodInSeconds + \" seconds exceeded\");\n          }\n        }\n        if (event.fingerprint.equals(fingerprint)) {\n          requestsByFingerprint++;\n          requestSizeByFingerprint += event.getSizeInBytes() * modeFactor;\n        }\n        if (ipFingerprintFactor > 0 && requestLimit > 0 && requestsByFingerprint > requestLimit) {\n          String msg = \"limit: \" + requestLimit + \" / \" + requestLimitPeriodInSeconds + \", requests: \"  + requestsByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n          throw new TooManyRequestsException(\"Client request limit of \" + requestLimit + \" requests per \" +\n            requestLimitPeriodInSeconds + \" seconds exceeded\"); }\n        if (requestLimit > 0 && requestsByIp > ipRequestLimit) {\n          String msg = \"limit: \" + ipRequestLimit + \" / \" + requestLimitPeriodInSeconds + \", requests: \"  + requestsByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n          throw new TooManyRequestsException(\"IP request limit of \" + ipRequestLimit + \" requests per \" +\n            requestLimitPeriodInSeconds + \" seconds exceeded\");\n        }\n        if (event.mode == JLanguageTool.Mode.TEXTLEVEL_ONLY) {\n          if (ipFingerprintFactor > 0 && requestLimitInBytes > 0 && requestSizeByFingerprint > requestLimitInBytes) {\n            String msg = \"limit in Mode.TEXTLEVEL_ONLY: \" + requestLimitInBytes + \" / \" + requestLimitPeriodInSeconds + \", request size: \"  + requestSizeByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n            throw new TooManyRequestsException(\"Client request size limit of \" + requestLimitInBytes + \" bytes per \" +\n              requestLimitPeriodInSeconds + \" seconds exceeded in text-level checks\");\n          }\n          if (requestLimitInBytes > 0 && requestSizeByIp > ipRequestLimitInBytes) {\n            String msg = \"limit in Mode.TEXTLEVEL_ONLY: \" + ipRequestLimitInBytes + \" / \" + requestLimitPeriodInSeconds + \", request size: \"  + requestSizeByIp + \", ip: \" + ipAddress + \", fingerprint: \" + fingerprint;\n            throw new TooManyRequestsException(\"IP request size limit of \" + ipRequestLimitInBytes + \" bytes per \" +\n              requestLimitPeriodInSeconds + \" seconds exceeded in text-level checks\");\n          }\n        } else {","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/RequestLimiter.java#L201-L237","documentation":"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.","triggerScenarios":"checkLimit detects requestsByFingerprint > requestLimit for one fingerprint within requestLimitPeriodInSeconds.","commonSituations":"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.","solutions":["Add client-side throttling: queue requests and keep under requestLimit per period","Increase the server's client requestLimit configuration","Stagger retries with exponential backoff and jitter instead of tight retry loops"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"class Limiter {\n  constructor(max, periodMs) { this.max = max; this.periodMs = periodMs; this.hits = []; }\n  async acquire() {\n    const now = Date.now();\n    this.hits = this.hits.filter(t => now - t < this.periodMs);\n    if (this.hits.length >= this.max) throw new Error('Client rate limit would be exceeded');\n    this.hits.push(now);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await check(text);\n} catch (e) {\n  if (e.status === 429 && /Client request limit/.test(e.message)) {\n    await backoff(attempt); // exponential backoff with jitter\n    return check(text);\n  }\n  throw e;\n}","preventionTips":["Serialize/throttle requests from each client instance","Add jitter to retries to avoid thundering herds","Monitor request rates per client token"],"tags":["rate-limit","languagetool","http-429"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}