SonarSource/sonarqube · error · IllegalArgumentException

Unexpected response from Bitbucket server

Error message

Unexpected response from Bitbucket server

What it means

validateResponseBody runs on successful (2xx) responses before the handler: it parses the body as a generic Gson Object to prove it is valid JSON. If parsing throws JsonParseException, the client throws IllegalArgumentException('Unexpected response from Bitbucket server') — the server replied 200 OK with a non-JSON body.

Source

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

  }

  protected static <G> G applyHandler(Function<String, G> handler, String bodyString) {
    try {
      return handler.apply(bodyString);
    } catch (JsonSyntaxException e) {
      LOG.info(UNABLE_TO_CONTACT_BITBUCKET_SERVER + ". Unexpected body response was : [{}]", bodyString);
      LOG.info(UNABLE_TO_CONTACT_BITBUCKET_SERVER + ": {}", e.getMessage(), e);
      throw new IllegalArgumentException(UNABLE_TO_CONTACT_BITBUCKET_SERVER + ", got an unexpected response", e);
    }
  }

  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) {

View on GitHub (pinned to 184c821202)

Solutions

  1. Inspect the logged body '[...]' in the server log to identify what the 200 response actually contained.
  2. Fix the server URL so REST calls hit Bitbucket directly rather than an SSO or proxy page.
  3. Bypass the SSO/proxy for SonarQube (IP allowlist or service account) so REST endpoints return JSON.
  4. Check proxy/load-balancer health-page behavior for the configured host.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  // call client
} catch (IllegalArgumentException e) {
  if ("Unexpected response from Bitbucket server".equals(e.getMessage())) {
    // 2xx but non-JSON: check the logged body, likely SSO login page or proxy HTML
  }
}

Prevention

When it happens

Trigger: An HTTP 200 response whose body is not JSON at all — e.g. an HTML SSO login page, a proxy maintenance page, or gzip/charset mangling — detected while calling any Bitbucket REST endpoint via getBodyString.

Common situations: SSO/SSO plugins redirecting REST calls to an HTML login form with 200 status; transparent proxies or load balancers serving error/maintenance HTML; misconfigured URL pointing to a web app root instead of the Bitbucket API host.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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