languagetool-org/languagetool · warning · UnavailableException
Server overloaded, please try again later
Error message
Server overloaded, please try again later
What it means
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.
Source
Thrown at languagetool-server/src/main/java/org/languagetool/server/TextChecker.java:630
int textSize = length;
List<CheckResults> ruleMatchesSoFar = Collections.synchronizedList(new ArrayList<>());
Future<List<CheckResults>> future;
try {
future = executorService.submit(() -> {
try (MDC.MDCCloseable c = MDC.putCloseable("rID", LanguageToolHttpHandler.getRequestId(httpExchange))) {
log.debug("Starting text check on {} chars; params: {}", length, qParams);
long time = System.currentTimeMillis();
List<CheckResults> results = getRuleMatches(aText, lang, motherTongue, params, qParams, userConfig, f -> ruleMatchesSoFar.add(new CheckResults(Collections.singletonList(f), Collections.emptyList())));
log.debug("Finished text check in {}ms. Starting suggestion generation.", System.currentTimeMillis() - time);
time = System.currentTimeMillis();
// generate suggestions, otherwise this is not part of the timeout logic and not properly measured in the metrics
results.stream().flatMap(r -> r.getRuleMatches().stream()).forEach(RuleMatch::computeLazySuggestedReplacements);
log.debug("Finished suggestion generation in {}ms, returning results.", System.currentTimeMillis() - time);
return results;
}
});
} catch (RejectedExecutionException e) {
throw new UnavailableException("Server overloaded, please try again later", e);
}
String incompleteResultReason = null;
List<CheckResults> res;
Attributes textCheckingAttributes = Attributes.builder()
.put("text.language", lang.getShortCode())
.put("text.size", textSize)
.put("userRules.size", userRules.size())
.put("dictionary.size", dictWords.size())
.build();
Integer finalCount = count;
Map.Entry<List<CheckResults>, String> resAndReason = TelemetryProvider.INSTANCE.createSpan(SPAN_NAME_PREFIX + "GetRuleMatches", textCheckingAttributes, (span) -> {
List<CheckResults> localRes;
String localReason = null;
try {
if (limits.getMaxCheckTimeMillis() < 0) {
localRes = future.get();
} else {
localRes = future.get(limits.getMaxCheckTimeMillis(), TimeUnit.MILLISECONDS);View on GitHub (pinned to 2e990059ce)
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
Example fix
// before results = Promise.all(texts.map(t => check(t))) // 50 parallel requests // after results = await pLimit(4)(texts.map(t => () => check(t))) // bounded concurrency + retry on 503
Defensive patterns
Strategy: retry
Validate before calling
// pre-check: keep concurrency below server capacity const limit = pLimit(Math.max(1, serverConfig.maxWorkers - 1));
Try / catch
async function checkWithRetry(text, tries = 3) {
for (let i = 0; i < tries; i++) {
try { return await check(text); }
catch (e) {
if ((e.status === 503 || /Server overloaded/.test(e.message)) && i < tries - 1) {
await sleep(500 * 2 ** i + Math.random() * 250);
continue;
}
throw e;
}
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Load spikes, bulk document checks issuing many parallel requests, undersized thread pool relative to traffic, or slow checks (huge texts) blocking the pool.
AI-assisted analysis of languagetool-org/languagetool@2e990059ce (2026-09-06).
Data as JSON: /api/errors/1b66cfbb6635fe8d.
Report an issue: GitHub.