SonarSource/sonarqube · warning · ServerException
The maximum number of concurrent calls for this web service…
Error message
The maximum number of concurrent calls for this web service has been reached
What it means
ConcurrentCallsLimitInterceptor.preAction enforces the @MaxConcurrentCalls-permit limit declared on a web service action. When all permits of the action's semaphore are held, it throws ServerException 503 telling the client the web service has reached its concurrency cap.
Solutions
- Retry the request after a delay with backoff; 503 here is transient once other calls finish.
- Reduce client-side concurrency (limit worker/parallelism count) below the server limit.
- Investigate why in-flight calls are slow and holding permits (DB, ES, downstream latency).
- If the limit is too low for legitimate traffic, raise the @MaxConcurrentCalls value on the server action.
Example fix
// before for (Project p : projects) executor.submit(() -> callSlowApi(p)); // 100 parallel // after ExecutorService pool = Executors.newFixedThreadPool(5); // stay under the limit for (Project p : projects) pool.submit(() -> retryOn503(() -> callSlowApi(p)));
Defensive patterns
Strategy: retry
Try / catch
if (response.code() == 503 && response.message().contains("concurrent calls")) { Thread.sleep(backoff); retry(); } Prevention
- Cap client parallelism below the server's declared limit
- Use exponential backoff with jitter on 503
- Monitor endpoint latency to spot permit exhaustion early
When it happens
Trigger: Sending more simultaneous requests than the action's annotation.value() permits to an endpoint annotated with a concurrency limit (key = action.path() + '/' + action.key()) while existing calls are still in flight.
Common situations: Load tests or parallel CI scans hammering a rate-limited endpoint; a slow downstream dependency causing calls to pile up and exhaust the semaphore; burst retries after a timeout.
Related errors
- a JVM option can't be empty and must start with '-'. The…
- a JVM option can't overwrite mandatory JVM options. The…
- a JVM option can't overwrite mandatory JVM options.
- A rule with the key ' ' already exists
- Authentication is required
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/23cee2398edce545.
Report an issue: GitHub.
Appendix: source
Thrown at server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/ConcurrentCallsLimitInterceptor.java:50
* When the maximum number of concurrent calls is reached, returns HTTP 503.
*/
public class ConcurrentCallsLimitInterceptor implements ActionInterceptor {
private final ConcurrentHashMap<String, Semaphore> semaphores = new ConcurrentHashMap<>();
private final ThreadLocal<Semaphore> acquiredSemaphore = new ThreadLocal<>();
@Override
public void preAction(WebService.Action action, Request request) {
ConcurrentCallsLimit annotation = action.handler().getClass().getAnnotation(ConcurrentCallsLimit.class);
if (annotation == null) {
return;
}
String key = action.path() + "/" + action.key();
Semaphore semaphore = semaphores.computeIfAbsent(key, k -> new Semaphore(annotation.value()));
if (semaphore.tryAcquire()) {
acquiredSemaphore.set(semaphore);
} else {
throw new ServerException(503, "The maximum number of concurrent calls for this web service has been reached");
}
}
@Override
public void postAction(WebService.Action action, Request request) {
Semaphore semaphore = acquiredSemaphore.get();
if (semaphore != null) {
semaphore.release();
acquiredSemaphore.remove();
}
}
}
View on GitHub (pinned to 184c821202)