{"record":{"id":"1b66cfbb6635fe8d","repo":"languagetool-org/languagetool","slug":"server-overloaded-please-try-again-later","errorCode":null,"errorMessage":"Server overloaded, please try again later","messagePattern":"Server overloaded, please try again later","errorType":"http","errorClass":"UnavailableException","httpStatus":null,"severity":"warning","filePath":"languagetool-server/src/main/java/org/languagetool/server/TextChecker.java","lineNumber":630,"sourceCode":"    int textSize = length;\n    List<CheckResults> ruleMatchesSoFar = Collections.synchronizedList(new ArrayList<>());\n    Future<List<CheckResults>> future;\n    try {\n      future = executorService.submit(() -> {\n        try (MDC.MDCCloseable c = MDC.putCloseable(\"rID\", LanguageToolHttpHandler.getRequestId(httpExchange))) {\n          log.debug(\"Starting text check on {} chars; params: {}\", length, qParams);\n          long time = System.currentTimeMillis();\n          List<CheckResults> results = getRuleMatches(aText, lang, motherTongue, params, qParams, userConfig, f -> ruleMatchesSoFar.add(new CheckResults(Collections.singletonList(f), Collections.emptyList())));\n          log.debug(\"Finished text check in {}ms. Starting suggestion generation.\", System.currentTimeMillis() - time);\n          time = System.currentTimeMillis();\n          // generate suggestions, otherwise this is not part of the timeout logic and not properly measured in the metrics\n          results.stream().flatMap(r -> r.getRuleMatches().stream()).forEach(RuleMatch::computeLazySuggestedReplacements);\n          log.debug(\"Finished suggestion generation in {}ms, returning results.\", System.currentTimeMillis() - time);\n          return results;\n        }\n      });\n    } catch (RejectedExecutionException e) {\n      throw new UnavailableException(\"Server overloaded, please try again later\", e);\n    }\n    String incompleteResultReason = null;\n    List<CheckResults> res;\n    Attributes textCheckingAttributes = Attributes.builder()\n            .put(\"text.language\", lang.getShortCode())\n            .put(\"text.size\", textSize)\n            .put(\"userRules.size\", userRules.size())\n            .put(\"dictionary.size\", dictWords.size())\n            .build();\n    Integer finalCount = count;\n    Map.Entry<List<CheckResults>, String> resAndReason = TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX + \"GetRuleMatches\", textCheckingAttributes, (span) -> {\n        List<CheckResults> localRes;\n        String localReason = null;\n        try {\n          if (limits.getMaxCheckTimeMillis() < 0) {\n            localRes = future.get();\n          } else {\n            localRes = future.get(limits.getMaxCheckTimeMillis(), TimeUnit.MILLISECONDS);","sourceCodeStart":612,"sourceCodeEnd":648,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/TextChecker.java#L612-L648","documentation":"LanguageTool server executes checks on a bounded pipeline/pool; when all workers are busy the executor rejects the task with RejectedExecutionException, which checkText translates into UnavailableException('Server overloaded, please try again later') — an HTTP 503-style transient condition, not a client bug.","triggerScenarios":"Submitting a /v2/check request while the server's pipeline pool is saturated (more concurrent requests than maxWorkerThreads/pipeline pool size), or after pool rejection from long-running checks hogging threads; hit by all checkText callers.","commonSituations":"Load spikes, bulk document checks issuing many parallel requests, undersized thread pool relative to traffic, or slow checks (huge texts) blocking the pool.","solutions":["Retry the request after a short backoff, ideally with exponential backoff and jitter","Reduce request concurrency in the client (serialize or cap parallel checks)","Split large texts to shorten per-request occupancy of the pool","If you operate the server, increase the pipeline pool / maxWorkerThreads configuration or scale horizontally"],"exampleFix":"// before\nresults = Promise.all(texts.map(t => check(t)))  // 50 parallel requests\n// after\nresults = await pLimit(4)(texts.map(t => () => check(t)))  // bounded concurrency + retry on 503","handlingStrategy":"retry","validationCode":"// pre-check: keep concurrency below server capacity\nconst limit = pLimit(Math.max(1, serverConfig.maxWorkers - 1));","typeGuard":null,"tryCatchPattern":"async function checkWithRetry(text, tries = 3) {\n  for (let i = 0; i < tries; i++) {\n    try { return await check(text); }\n    catch (e) {\n      if ((e.status === 503 || /Server overloaded/.test(e.message)) && i < tries - 1) {\n        await sleep(500 * 2 ** i + Math.random() * 250);\n        continue;\n      }\n      throw e;\n    }\n  }\n}","preventionTips":["Bound client concurrency; avoid unbounded Promise.all over many texts","Retry 503s with exponential backoff and jitter","Split huge texts so checks finish faster and free pool workers","Size the server pipeline pool to peak traffic"],"tags":["overload","http-503","retry"],"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"}