SonarSource/sonarqube · error · BitbucketServerException

Invalid personal access token

Error message

Invalid personal access token

What it means

handleHttpErrorIfAny inspects non-2xx responses. On HTTP 401 (Unauthorized) it throws BitbucketServerException(401, 'Invalid personal access token'), meaning Bitbucket rejected the configured personal access token's credentials. Other codes map to 404 'server unreachable' or a generic IllegalArgumentException.

Source

Thrown at server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClient.java:188

  }

  protected static void validateResponseBody(boolean isSuccessful, String bodyString) {
    if (isSuccessful) {
      try {
        buildGson().fromJson(bodyString, Object.class);
      } catch (JsonParseException e) {
        LOG.info(UNEXPECTED_RESPONSE_FROM_BITBUCKET_SERVER + " : [{}]", bodyString);
        throw new IllegalArgumentException(UNEXPECTED_RESPONSE_FROM_BITBUCKET_SERVER, e);
      }
    }
  }

  protected static void handleHttpErrorIfAny(boolean isSuccessful, int httpCode, String bodyString) {
    if (!isSuccessful) {
      String errorMessage = getErrorMessage(bodyString);
      LOG.info(UNABLE_TO_CONTACT_BITBUCKET_SERVER + ": {} {}", httpCode, errorMessage);
      if (httpCode == HTTP_UNAUTHORIZED) {
        throw new BitbucketServerException(HTTP_UNAUTHORIZED, "Invalid personal access token");
      } else if (httpCode == HTTP_NOT_FOUND) {
        throw new BitbucketServerException(HTTP_NOT_FOUND, "Error 404. The requested Bitbucket server is unreachable.");
      }
      throw new IllegalArgumentException(UNABLE_TO_CONTACT_BITBUCKET_SERVER);
    }
  }

  protected static boolean equals(@Nullable MediaType first, @Nullable MediaType second) {
    String s1 = convertMediaTypeToString(first);
    String s2 = convertMediaTypeToString(second);
    return s1 != null && s1.equals(s2);
  }

  private static String convertMediaTypeToString(@Nullable MediaType mediaType) {
    return Optional.ofNullable(mediaType)
      .map(MediaType::toString)
      .map(s -> s.toLowerCase(ENGLISH).replace(" ", ""))
      .orElse(null);

View on GitHub (pinned to 184c821202)

Solutions

  1. Generate a new personal access token in Bitbucket (with REPO_READ or required project permissions) and update it in the SonarQube ALM integration setting.
  2. Verify the token belongs to a still-active user on the Bitbucket instance referenced by serverUrl.
  3. Confirm the token was not truncated/prefixed — paste it again without whitespace.
  4. Test the token directly: curl -H 'Authorization: Bearer <token>' <serverUrl>/rest/api/1.0/inbox.

Example fix

// before: almSetting pat = expired/revoked token
// after: create a new PAT in Bitbucket > Account > Personal access tokens and update the SonarQube ALM setting
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the token before configuring:
curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer <pat>" <serverUrl>/rest/api/1.0/inbox
// expect 200; 401 means the token is invalid/expired/lacks permissions

Try / catch

try {
  // call client
} catch (BitbucketServerException e) {
  if (e.getHttpStatusCode() == 401) {
    // prompt admin to refresh the personal access token in ALM settings
  }
}

Prevention

When it happens

Trigger: Calling any Bitbucket REST endpoint when the PAT bound to the ALM setting is revoked, expired, mistyped, lacks the required permissions (e.g. REPO_READ), or belongs to a disabled user — Bitbucket returns 401 and this exception is raised by getBodyString.

Common situations: PAT expired per Bitbucket's token expiry policy; token rotated/revoked after a user left; token pasted with whitespace or from the wrong Bitbucket instance; user account deactivated; token created without repository read permission.

Related errors


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/a9981328edb8fc79. Report an issue: GitHub.