languagetool-org/languagetool · warning · TooManyRequestsException

Client request size limit of bytes per seconds exceeded

Error message

Client request size limit of  bytes per  seconds exceeded

What it means

Outside TEXTLEVEL_ONLY mode, RequestLimiter enforces a per-fingerprint request-size quota on all checks. When bytes sent under one fingerprint exceed requestLimitInBytes within requestLimitPeriodInSeconds, checkLimit throws TooManyRequestsException (HTTP 429). This is the general (non-text-level-only) client size cap.

Source

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

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

    private final String ip;
    private final Date date;
    private final int sizeInBytes;
    private final String fingerprint;

View on GitHub (pinned to 2e990059ce)

Solutions

  1. Wait for the limit period to reset, then send fewer/smaller requests
  2. Split large texts into chunks below the per-window size budget and pace requests
  3. Authenticate with Premium credentials (username/apiKey) for higher limits
  4. If operating the server, increase requestLimitInBytes (e.g. --limitRequestSizeInBytes) or adjust ipFingerprintFactor/requestLimitPeriodInSeconds
  5. Avoid reusing an identical fingerprint-heavy request pattern; vary/reduce what is sent or use authenticated access

Example fix

// before
resp = check(mode="all", text=wholeBook);
// after
chunks = split(wholeBook, 20000);
for c in chunks: checkWithBackoff(mode="all", text=c);
Defensive patterns

Strategy: retry

Validate before calling

if (new Blob([text]).size > MAX_CHUNK_BYTES) text = chunk(text, MAX_CHUNK_BYTES);

Try / catch

try {
  return await check(text, mode);
} catch (e) {
  if (e.status === 429) { await exponentialBackoff(); return check(text, mode); }
  throw e;
}

Prevention

When it happens

Trigger: POSTing to /v2/check (mode=all/allButTextLevelOnly/unset) when the fingerprint-weighted cumulative request size exceeds requestLimitInBytes within requestLimitPeriodInSeconds, with ipFingerprintFactor > 0 and requestLimitInBytes > 0.

Common situations: Free/public API users submitting very large texts; apps checking whole documents per keystroke; shared fingerprint (same browser/params) across many requests; low requestLimitInBytes set in server config.

Related errors


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