{"record":{"id":"9dfc051912030083","repo":"SonarSource/sonarqube","slug":"gitlab-api-rate-limit-exceeded-try-again-later","errorCode":null,"errorMessage":"GitLab API rate limit exceeded. Try again later.","messagePattern":"GitLab API rate limit exceeded\\. Try again later\\.","errorType":"exception","errorClass":"GitlabServerException","httpStatus":429,"severity":"warning","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java","lineNumber":220,"sourceCode":"\n  protected static void checkResponseIsSuccessful(Response response) throws IOException {\n    checkResponseIsSuccessful(response, \"GitLab Merge Request did not happen, please check your configuration\");\n  }\n\n  protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException {\n    if (!response.isSuccessful()) {\n      String body = response.body().string();\n      LOG.error(\"Gitlab API call to [{}] failed with {} http code. gitlab response content : [{}]\", response.request().url(), response.code(), body);\n      if (isTokenRevoked(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token was revoked\");\n      } else if (isTokenExpired(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token is expired\");\n      } else if (isInsufficientScope(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token has insufficient scope\");\n      } else if (response.code() == HTTP_FORBIDDEN) {\n        throw new GitlabServerException(response.code(), \"Forbidden access to GitLab. Verify your token's permissions and IP restrictions.\");\n      } else if (response.code() == HTTP_TOO_MANY_REQUESTS) {\n        throw new GitlabServerException(response.code(), \"GitLab API rate limit exceeded. Try again later.\");\n      } else if (response.code() == HTTP_UNAUTHORIZED) {\n        throw new GitlabServerException(response.code(), \"Invalid personal access token\");\n      } else if (response.isRedirect()) {\n        throw new GitlabServerException(response.code(), \"Request was redirected, please provide the correct URL\");\n      } else {\n        throw new GitlabServerException(response.code(), errorMessage);\n      }\n    }\n  }\n\n  private static boolean isTokenRevoked(Response response, String body) {\n    if (response.code() == HTTP_UNAUTHORIZED) {\n      try {\n        Optional<GsonError> gitlabError = GsonError.parseOne(body);\n        return gitlabError.map(GsonError::getErrorDescription).map(description -> description.contains(\"Token was revoked\")).orElse(false);\n      } catch (JsonParseException e) {\n        // nothing to do\n      }","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java#L202-L238","documentation":"checkResponseIsSuccessful translates GitLab's HTTP 429 (Too Many Requests) into GitlabServerException 'GitLab API rate limit exceeded. Try again later.' SonarQube made more GitLab API requests than the instance allows per window; the call failed client-throttling, not because of configuration.","triggerScenarios":"Any GitLab API call via checkResponseIsSuccessful (checkProjectAccess, checkToken, getPersonalAccessTokenInfo, checkWritePermission, createProjectAccessToken) receives 429 at GitlabApplicationClient.java:220 — typically during bulk imports or CI-heavy periods with many concurrent analyses.","commonSituations":"Large onboarding importing many GitLab projects at once; many CI jobs starting analyses simultaneously; GitLab instance or reverse proxy (nginx) with low rate limits; shared NAT making many SonarQube requests appear from one IP.","solutions":["Wait for the rate-limit window to reset and retry the operation.","Reduce parallelism: import/analyze projects in smaller batches or stagger CI jobs.","Raise rate limits in GitLab (Admin Area > Settings > Network > User and IP rate limits) or on the reverse proxy.","Ensure SonarQube traffic is authenticated (authenticated users get higher limits than anonymous)."],"exampleFix":"// before: import all 500 projects at once\nparallelImport(projects);\n// after: batch with delay\nprojects.stream().collect(batching(50)).forEach(batch -> { importBatch(batch); sleep(backoff); });","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (GitlabServerException e) {\n  if (\"GitLab API rate limit exceeded. Try again later.\".equals(e.getMessage())) {\n    long wait = Math.min(60, (long) Math.pow(2, attempt)) * 1000L; // exponential backoff, max 60s\n    Thread.sleep(wait);\n    retry();\n  } else { throw e; }\n}","preventionTips":["Throttle bulk project imports and stagger CI-triggered analyses.","Tune GitLab rate limits for the SonarQube server's traffic volume.","Monitor 429 occurrences and back off automatically instead of hot-looping."],"tags":["gitlab","rate-limit","http-429","network"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}