languagetool-org/languagetool · error · TooManyRequestsException

IP request limit of requests per seconds exceeded

Error message

IP request limit of  requests per  seconds exceeded

What it means

HTTP 429 error thrown when the total number of requests from one IP address exceeds ipRequestLimit within requestLimitPeriodInSeconds. This is the per-IP guard, regardless of fingerprint, protecting the server from high-volume sources.

Source

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

          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 {
          if (ipFingerprintFactor > 0 && requestLimitInBytes > 0 && requestSizeByFingerprint > requestLimitInBytes) {
            String msg = "limit: " + requestLimitInBytes + " / " + requestLimitPeriodInSeconds + ", request size: "  + requestSizeByIp + ", ip: " + ipAddress + ", fingerprint: " + fingerprint;
            throw new TooManyRequestsException("Client request size limit of " + requestLimitInBytes + " bytes per " +
              requestLimitPeriodInSeconds + " seconds exceeded");

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Reduce request rate or batch text checks into fewer, larger requests
  2. Raise ipRequestLimit in the server configuration if the source is trusted
  3. Route legitimate high-volume clients through distinct IPs/credentials or wait out the rate-limit window
Defensive patterns

Strategy: retry

Validate before calling

const MIN_INTERVAL_MS = (periodSeconds * 1000) / ipRequestLimit;
let last = 0;
async function throttle() {
  const wait = last + MIN_INTERVAL_MS - Date.now();
  if (wait > 0) await new Promise(r => setTimeout(r, wait));
  last = Date.now();
}

Try / catch

try {
  return await check(text);
} catch (e) {
  if (e.status === 429 && /IP request limit/.test(e.message)) {
    await sleep(periodSeconds * 1000);
    return check(text);
  }
  throw e;
}

Prevention

When it happens

Trigger: checkLimit detects requestsByIp > ipRequestLimit while requestLimit > 0 within the period.

Common situations: Many users behind one NAT/proxy IP collectively exceeding the limit, load tests run against production, or microservices sharing an egress IP hitting the API concurrently.

Related errors


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