SonarSource/sonarqube · error · BitbucketCloudException
Error returned by Bitbucket Cloud: %s [HTTP %s] | Unable to
Error message
Error returned by Bitbucket Cloud: %s [HTTP %s] | Unable to contact Bitbucket Cloud servers [HTTP %s]
What it means
handleError builds the failure message for any non-2xx Bitbucket Cloud API response and throws BitbucketCloudException(errorMessage, statusCode). If the response body was JSON with a parseable 'error.message', the message is 'Error returned by Bitbucket Cloud: <parsed msg> [HTTP <code>]'; otherwise it falls back to 'Unable to contact Bitbucket Cloud servers [HTTP <code>]'. The status code (401, 403, 404, 429, 5xx...) is the real signal about what went wrong.
Source
Thrown at server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java:247
}
return handler.apply(response);
} catch (IOException e) {
LOG.info(ERROR_BBC_SERVERS + ": {}", e.getMessage());
throw new IllegalStateException(ERROR_BBC_SERVERS, e);
}
}
private static void handleError(Response response) throws IOException {
ErrorDetails error = getError(response.body(), response.message());
int statusCode = response.code();
LOG.atInfo().log(() -> String.format(BBC_FAIL_WITH_RESPONSE, response.request().url(), statusCode, error.body));
String errorMessage;
if (error.parsedErrorMsg != null) {
errorMessage = ERROR_BBC_SERVERS + ": " + error.parsedErrorMsg + " [HTTP " + statusCode + "]";
} else {
errorMessage = UNABLE_TO_CONTACT_BBC_SERVERS + " [HTTP " + statusCode + "]";
}
throw new BitbucketCloudException(errorMessage, statusCode);
}
private static ErrorDetails getError(@Nullable ResponseBody body, @Nullable String fallbackMessage) throws IOException {
return getErrorDetails(body, fallbackMessage, s -> {
Error gsonError = buildGson().fromJson(s, Error.class);
if (gsonError != null && gsonError.errorMsg != null && gsonError.errorMsg.message != null) {
return gsonError.errorMsg.message;
}
return null;
});
}
private static ErrorDetails getTokenError(@Nullable ResponseBody body, @Nullable String fallbackMessage) throws IOException {
if (body == null) {
return new ErrorDetails(fallbackMessage, null);
}
String bodyStr = body.string();
if (bodyStr.isBlank()) {View on GitHub (pinned to 184c821202)
Solutions
- Parse the '[HTTP <code>]' suffix from the message and branch on it — the code is the authoritative cause.
- 401/403: regenerate the access token or API token and ensure the OAuth consumer/token has the required scopes (repository read, pull request read).
- 404: verify the workspace and repository slugs passed to getRepo/searchRepos match Bitbucket Cloud exactly.
- 429: add/exponential backoff in the caller and reduce polling frequency; honor the Retry-After header.
- 5xx or HTML-body responses: retry with backoff and check the Bitbucket Cloud status page; treat it as transient.
Example fix
// before: treating every BitbucketCloudException the same
try { client.getRepo(cred, ws, slug); } catch (BitbucketCloudException e) { retry(); }
// after: branch on the embedded status code
try { client.getRepo(cred, ws, slug); }
catch (BitbucketCloudException e) {
if (e.getHttpCode() == 404) { throw new IllegalArgumentException("Unknown repo " + slug); }
if (e.getHttpCode() == 429 || e.getHttpCode() >= 500) { backoffAndRetry(); }
else { throw new IllegalStateException("Auth/permission problem: " + e.getMessage(), e); }
} Defensive patterns
Strategy: type-guard
Validate before calling
// Before calling the API, validate inputs that drive the URL and auth:
static void precheck(String workspace, String slug, String token) {
if (workspace == null || !workspace.matches("[A-Za-z0-9][A-Za-z0-9_.-]*"))
throw new IllegalArgumentException("Invalid workspace slug: prevents 404s");
if (slug != null && !slug.matches("[A-Za-z0-9_.-]+"))
throw new IllegalArgumentException("Invalid repo slug: prevents 404s");
if (token == null || token.isBlank())
throw new IllegalArgumentException("Blank token: prevents 401s");
} Type guard
// Narrow BitbucketCloudException by its embedded HTTP status before reacting:
static boolean isAuthError(BitbucketCloudException e) {
return e.getHttpCode() == 401 || e.getHttpCode() == 403;
}
static boolean isNotFound(BitbucketCloudException e) { return e.getHttpCode() == 404; }
static boolean isRateLimited(BitbucketCloudException e) { return e.getHttpCode() == 429; }
static boolean isTransient(BitbucketCloudException e) {
return e.getHttpCode() == 429 || e.getHttpCode() >= 500;
} Try / catch
try {
return restClient.getRepo(cred, workspace, slug);
} catch (BitbucketCloudException e) {
int code = e.getHttpCode();
if (code == 401 || code == 403) {
throw new ConfigurationException("Bitbucket token invalid or missing scopes (HTTP " + code + ")", e);
} else if (code == 404) {
throw new IllegalArgumentException("Workspace/repo not found: '" + workspace + "/" + slug + "'");
} else if (code == 429 || code >= 500) {
throw new TransientApiException("Bitbucket Cloud temporarily failing (HTTP " + code + "), retry with backoff", e);
}
throw e; // includes the '[HTTP n]' suffix and any parsed Bitbucket message
} Prevention
- Always branch on getHttpCode() rather than the message text — the code is stable, the wording is not.
- Keep tokens valid and scoped (repository + pull request read) to avoid 401/403.
- Spell-check workspace/repository slugs; 404s are the most common avoidable cause.
- Implement exponential backoff for 429/5xx and honor Retry-After headers to stay under rate limits.
- Log the request URL alongside failures (mirroring the client's INFO logs) so failures are debuggable later.
When it happens
Trigger: Thrown by handleError, invoked from doCall (callers: createAccessToken, doGet, doGetWithApiToken) whenever response.isSuccessful() is false on calls to api.bitbucket.org/2.0/... : 401 invalid credentials, 403 missing permission, 404 unknown workspace/repo, 429 rate limited, 5xx server errors; or when the error body is not JSON / lacks error.errorMsg.message so no parsed message is available.
Common situations: Expired or revoked access/API token (401); OAuth token lacking the 'pullrequest' scope; querying a workspace slug that does not exist or is misspelled (404); hitting Bitbucket Cloud rate limits on polling integrations (429); transient 502/503 during Bitbucket incidents where the body is HTML so no parsed message is available; firewall returning a captive-portal HTML page.
Related errors
- Error returned by Bitbucket Cloud: The OAuth client in the B
- e.getMessage()
- Error returned by Bitbucket Cloud: %s
- Error returned by Bitbucket Cloud
- GitLab API rate limit exceeded. Try again later.
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/eabc988eafe6b60e.
Report an issue: GitHub.