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
- Query api/qualityprofiles/search and mark another profile as default (api/qualityprofiles/set_default) for the affected language
- Delete the descendant default profile first, or unset its default flag, then delete the target profile
- 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
- Always call api/qualityprofiles/search and check isDefault before deleting
- Unset the default flag on parent and child profiles first
- In bulk-delete scripts, resolve descendants (api/qualityprofiles/children) beforehand
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
- Backup XML is not valid. Root element must be
- Fail to restore Quality profile backup, XML document is not…
- Quality profile not found
- Source and target profiles are equal
- The quality profile cannot be restored as it contains rules…
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)