SonarSource/sonarqube · error · IllegalArgumentException

Profile ' ' cannot be deleted because its descendant named…

Error message

Profile '%s' cannot be deleted because its descendant named '%s' is marked as default

What it means

DeleteAction.ensureNoneIsMarkedAsDefault refuses to delete a quality profile when the profile itself or any of its descendant (child) profiles is marked as default. Deleting a default profile would leave the language without an active default, so the operation is rejected with this IllegalArgumentException.

Solutions

  1. Query api/qualityprofiles/search and mark another profile as default (api/qualityprofiles/set_default) for the affected language
  2. Delete the descendant default profile first, or unset its default flag, then delete the target profile
  3. Adjust automation to skip or handle profiles listed as default in the search response

Example fix

// before
curl -X POST api/qualityprofiles/delete?language=java&qualityProfile=MyProfile
// after
curl -X POST api/qualityprofiles/set_default?id=AXSonar_way
sleep 1
curl -X POST api/qualityprofiles/delete?language=java&qualityProfile=MyProfile
Defensive patterns

Strategy: validation

Validate before calling

// before deleting, check profile and descendants for default flags
const profiles = await get("api/qualityprofiles/search?language=" + lang);
const target = profiles.profiles.find(p => p.key === key);
const blockers = profiles.profiles.filter(p => p.name === target.name || p.parentName === target.name).filter(p => p.isDefault);
if (blockers.length > 0) await post("api/qualityprofiles/set_default", { id: fallbackProfileKey });

Type guard

boolean isSafeToDelete(QProfileSummary p) {
  return !p.isDefault;
}

Try / catch

try {
  post("api/qualityprofiles/delete", { key });
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("is marked as default")) {
    setAnotherProfileAsDefaultForSameLanguage();
  }
}

Prevention

When it happens

Trigger: Calling api/qualityprofiles/delete for a profile whose kee is in uuidsOfDefaultProfiles, or for a profile that has at least one child profile (built from it) that is the default profile for its language.

Common situations: Bulk cleanup scripts deleting profiles without checking which ones are default; deleting a parent profile after a team copied it and made the copy the default; automation that assumes non-default parents can always be removed.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/DeleteAction.java:106

  }

  private Collection<QProfileDto> selectDescendants(DbSession dbSession, QProfileDto profile) {
    return dbClient.qualityProfileDao().selectDescendants(dbSession, singleton(profile));
  }

  private void ensureNoneIsMarkedAsDefault(DbSession dbSession, QProfileDto profile, Collection<QProfileDto> descendants) {
    Set<String> allUuids = new HashSet<>();
    allUuids.add(profile.getKee());
    descendants.forEach(p -> allUuids.add(p.getKee()));

    Set<String> uuidsOfDefaultProfiles = dbClient.defaultQProfileDao().selectExistingQProfileUuids(dbSession, allUuids);

    checkArgument(!uuidsOfDefaultProfiles.contains(profile.getKee()), "Profile '%s' cannot be deleted because it is marked as default", profile.getName());
    descendants.stream()
      .filter(p -> uuidsOfDefaultProfiles.contains(p.getKee()))
      .findFirst()
      .ifPresent(p -> {
        throw new IllegalArgumentException(String.format("Profile '%s' cannot be deleted because its descendant named '%s' is marked as default", profile.getName(), p.getName()));
      });
  }

  private static List<QProfileDto> merge(QProfileDto profile, Collection<QProfileDto> descendants) {
    return Stream.concat(Stream.of(profile), descendants.stream())
      .toList();
  }
}

View on GitHub (pinned to 184c821202)