{"record":{"id":"7ff8695112591cf7","repo":"SonarSource/sonarqube","slug":"url-must-start-with-http-or-https","errorCode":null,"errorMessage":"url must start with http:// or https://","messagePattern":"url must start with http:// or https://","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClient.java","lineNumber":121,"sourceCode":"  public RepositoryList getRecentRepo(String serverUrl, String token) {\n    HttpUrl url = buildUrl(serverUrl, \"/rest/api/1.0/profile/recent/repos\");\n    return doGet(token, url, body -> buildGson().fromJson(body, RepositoryList.class));\n  }\n\n  public ProjectList getProjects(String serverUrl, String token, @Nullable Integer start, int pageSize) {\n    String startOrEmpty = Optional.ofNullable(start).map(String::valueOf).orElse(\"\");\n    HttpUrl url = buildUrl(serverUrl, format(\"/rest/api/1.0/projects?start=%s&limit=%s\", startOrEmpty, pageSize));\n    return doGet(token, url, body -> buildGson().fromJson(body, ProjectList.class));\n  }\n\n  public BranchesList getBranches(String serverUrl, String token, String projectSlug, String repositorySlug) {\n    HttpUrl url = buildUrl(serverUrl, format(\"/rest/api/1.0/projects/%s/repos/%s/branches\", projectSlug, repositorySlug));\n    return doGet(token, url, body -> buildGson().fromJson(body, BranchesList.class));\n  }\n\n  protected static HttpUrl buildUrl(@Nullable String serverUrl, String relativeUrl) {\n    if (serverUrl == null || !(serverUrl.toLowerCase(ENGLISH).startsWith(\"http://\") || serverUrl.toLowerCase(ENGLISH).startsWith(\"https://\"))) {\n      throw new IllegalArgumentException(\"url must start with http:// or https://\");\n    }\n    return HttpUrl.parse(CS.removeEnd(serverUrl, \"/\") + relativeUrl);\n  }\n\n  protected <G> G doGet(String token, HttpUrl url, Function<String, G> handler) {\n    Request request = prepareRequestWithBearerToken(token, GET, url, null);\n    return doCall(request, handler);\n  }\n\n  protected static Request prepareRequestWithBearerToken(@Nullable String token, String method, HttpUrl url, @Nullable RequestBody body) {\n    Request.Builder builder = new Request.Builder()\n      .method(method, body)\n      .url(url)\n      .addHeader(\"x-atlassian-token\", \"no-check\")\n      .addHeader(\"Accept\", \"application/json\");\n\n    if (!isNullOrEmpty(token)) {\n      builder.addHeader(\"Authorization\", \"Bearer \" + token);","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucketserver/BitbucketServerRestClient.java#L103-L139","documentation":"BitbucketServerRestClient.buildUrl validates the configured Bitbucket server URL before building the REST endpoint. If serverUrl is null or does not start with http:// or https:// (case-insensitive), an IllegalArgumentException with this message is thrown. The library requires a fully-qualified absolute URL for the ALM server setting.","triggerScenarios":"Calling any BitbucketServerRestClient method (e.g. url(projectSlug, repositorySlug)) when the almSettings/bitbucket serverUrl is null, or set to a scheme-less value like 'my-bitbucket.example.com', 'bitbucket.example.com/', 'ftp://...', or 'localhost:7990'.","commonSituations":"Administrators entering the Bitbucket Server host without a scheme in project ALM settings; settings imported from old configs or database rows with a null serverUrl; using an env placeholder that resolves to empty; SonarQube instances where the Bitbucket integration was configured before a scheme was required.","solutions":["Set the Bitbucket server URL to an absolute URL including the scheme, e.g. https://bitbucket.example.com (Administration > ALM Integrations, or update almSetting via web API).","If serverUrl is null, the ALM setting is missing or misnamed — create/verify the Bitbucket ALM configuration bound to the project.","Strip trailing slashes and any path you expect the client to add; the client appends /rest/api/1.0/... itself."],"exampleFix":"// before: almSetting serverUrl = \"bitbucket.mycompany.com\"\n// after: serverUrl = \"https://bitbucket.mycompany.com\"","handlingStrategy":"validation","validationCode":"String serverUrl = /* configured Bitbucket server URL */;\nif (serverUrl == null || !(serverUrl.toLowerCase(Locale.ENGLISH).startsWith(\"http://\") || serverUrl.toLowerCase(Locale.ENGLISH).startsWith(\"https://\"))) {\n  throw new IllegalArgumentException(\"Bitbucket server URL must be an absolute http(s) URL, got: \" + serverUrl);\n}","typeGuard":"static boolean isValidServerUrl(String url) {\n  return url != null && (url.toLowerCase(Locale.ENGLISH).startsWith(\"http://\") || url.toLowerCase(Locale.ENGLISH).startsWith(\"https://\"));\n}","tryCatchPattern":null,"preventionTips":["Always store the Bitbucket base URL with an explicit http:// or https:// scheme.","Validate the ALM server URL at configuration-save time in the UI/API, not at request time.","Avoid trailing slashes and relative hostnames in stored settings."],"tags":["java","bitbucket-server","url-validation","configuration"],"backgroundTag":"invalid-url-format","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"}