{"record":{"id":"e6240745a5fdad0f","repo":"SonarSource/sonarqube","slug":"skipping-project-binding-from-search-results","errorCode":null,"errorMessage":"Skipping project binding '{}' from search results: failed to build it: {} ({})","messagePattern":"Skipping project binding '(.+?)' from search results: failed to build it: (.+?) \\((.+?)\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ProjectBindingsServiceServerImpl.java","lineNumber":139,"sourceCode":"        .filter(Objects::nonNull)\n        .toList();\n      return new ProjectBindings(bindings, new PageRestResponse(1, bindings.size(), bindings.size()));\n    }\n  }\n\n  /**\n   * Same as {@link #toProjectBinding}, but never lets one binding's failure (e.g. a concurrently deleted ALM\n   * setting) abort the whole search — unlike {@link #getProjectBinding}, which is about exactly one binding and\n   * can afford to let such an inconsistency surface as an error.\n   */\n  @Nullable\n  private ProjectBinding toProjectBindingOrSkip(DbSession dbSession, ProjectAlmSettingDto projectAlmSetting, Map<String, String> bitbucketCloudTokenCache) {\n    try {\n      return toProjectBinding(dbSession, projectAlmSetting, bitbucketCloudTokenCache);\n    } catch (Exception e) {\n      // Same rationale as resolveLive/persist: never log \"e\" directly, since an exception reaching this catch\n      // could in principle carry ALM-influenced content from a lower layer.\n      LOG.warn(\"Skipping project binding '{}' from search results: failed to build it: {} ({})\", projectAlmSetting.getUuid(),\n        sanitizeForLog(String.valueOf(e.getMessage())), e.getClass().getSimpleName());\n      return null;\n    }\n  }\n\n  private static void validateQuery(ProjectBindingsQuery query) {\n    boolean hasProjectId = isNotBlank(query.projectId());\n    boolean hasUrl = isNotBlank(query.url());\n    boolean hasOrganizationId = isNotBlank(query.organizationId());\n    boolean hasDevOpsPlatform = isNotBlank(query.devOpsPlatform());\n    boolean hasRepositoryId = isNotBlank(query.repositoryId());\n\n    if (hasDevOpsPlatform != hasRepositoryId) {\n      throw BadRequestException.create(\"devOpsPlatform and repositoryId must be provided together\");\n    }\n    int paramCount = (hasProjectId ? 1 : 0) + (hasUrl ? 1 : 0) + (hasOrganizationId ? 1 : 0) + (hasDevOpsPlatform ? 1 : 0);\n    if (paramCount > 1) {\n      throw BadRequestException.create(\"Only one of projectId, url, organizationId, or devOpsPlatform+repositoryId can be provided\");","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ProjectBindingsServiceServerImpl.java#L121-L157","documentation":"This warning is logged by ProjectBindingsServiceServerImpl.toProjectBindingOrSkip when building a single ProjectBinding from a ProjectAlmSettingDto throws any exception while processing a project-bindings search. Instead of failing the whole search, the binding's UUID is logged (with a sanitized message and exception class name only, never the raw throwable) and the binding is skipped from the results.","triggerScenarios":"GET api/project_bindings/search (or equivalent internal search) where one project's binding references an ALM setting that is broken: deleted ALM setting row, unreachable ALM server, invalid stored url/repoId, or an ALM REST client throwing during live resolution.","commonSituations":"An ALM setting was deleted while projects were still bound to it; an ALM instance went down mid-search; legacy rows with null almRepo/almSlug; post-migration data inconsistencies.","solutions":["Identify the skipped binding UUID from the log and inspect its projectAlmSetting row via api/alm_settings/list or api/project_bindings/search with a filter","Re-bind the affected project with api/alm_settings/set_binding using a valid ALM setting and repository identifiers","Check that the referenced ALM setting still exists and its URL/credentials are valid","If many bindings are skipped, check ALM server reachability from the SonarQube host"],"exampleFix":"// before: dangling binding\nDELETE the orphan binding or re-bind\n// after\ncurl -X POST -u admin:token 'https://sonar.example.com/api/alm_settings/set_binding' -d 'project=proj_key' -d 'almSettingKey=github1' -d 'almRepo=org/repo'","handlingStrategy":"validation","validationCode":"// Before searching, confirm the binding's ALM setting still exists and repo fields are populated\nAlmSettingDto setting = dbClient.almSettingDao().selectByKey(dbSession, almSettingKey);\nboolean ok = setting != null && setting.getUrl() != null && projectAlmSetting.getAlmRepo() != null;","typeGuard":null,"tryCatchPattern":"// Consumers of project_bindings/search: tolerate missing entries and re-check skipped UUIDs from logs\nList<ProjectBinding> bindings = service.bindings(query);\nif (bindings.size() < expected) { log.warn(\"Some bindings skipped; check server warnings for UUIDs\"); }","preventionTips":["Delete or re-bind projects when removing an ALM setting","Null-check almRepo/almSlug after data imports or migrations","Monitor ALM server availability — outages surface as skipped bindings","Correlate skipped binding UUIDs in logs with project keys to find stale rows"],"tags":["alm-integration","project-binding","search","resilience","logging"],"backgroundTag":"resource-not-found","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"}