alibaba/nacos · error · IllegalArgumentException

role 'ROLE_ADMIN' is not permitted to delete!

Error message

role 'ROLE_ADMIN' is not permitted to delete!

What it means

Thrown by AbstractCheckedRoleService.rejectReservedRole when an attempt is made to delete (or in some paths, manually create) the ROLE_ADMIN role. Nacos designates GLOBAL_ADMIN_ROLE (ROLE_ADMIN) as a system-reserved role that cannot be removed through the API. The rejectReservedRole method is called by role deletion and creation flows to enforce this invariant.

Solutions

  1. Do not attempt to delete ROLE_ADMIN — it is required by the system for administrative access.
  2. If you need to reset admin access, change the admin user's password or recreate the user, not the role.
  3. Filter system-reserved roles out of any automated cleanup or migration scripts.
  4. If building a role management UI, disable the delete button for system-reserved roles.

Example fix

// before
roleService.deleteRole("ROLE_ADMIN"); // throws IllegalArgumentException

// after
if (!AuthConstants.GLOBAL_ADMIN_ROLE.equals(role) && !AuthConstants.ANONYMOUS_ROLE.equals(role)) {
    roleService.deleteRole(role);
} else {
    return Result.failed("Cannot delete system-reserved role: " + role);
}
Defensive patterns

Strategy: validation

Validate before calling

// Check for reserved role before attempting deletion
Set<String> reservedRoles = Set.of(
    AuthConstants.GLOBAL_ADMIN_ROLE,
    AuthConstants.ANONYMOUS_ROLE
);
if (reservedRoles.contains(roleName)) {
    throw new IllegalArgumentException("Cannot delete system-reserved role: " + roleName);
}
roleService.deleteRole(roleName);

Type guard

public static boolean isReservedRole(String role) {
    return AuthConstants.GLOBAL_ADMIN_ROLE.equals(role)
        || AuthConstants.ANONYMOUS_ROLE.equals(role);
}

Try / catch

try {
    roleService.deleteRole(roleName);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("not permitted to delete")) {
        return Result.failed("Role '" + roleName + "' is system-reserved and cannot be deleted");
    }
    throw e;
}

Prevention

When it happens

Trigger: An admin API call (e.g., DELETE /v3/admin/auth/roles?role=ROLE_ADMIN) targets the global admin role. The rejectReservedRole guard intercepts this before the deletion reaches the persistence layer. Also triggered if a role-creation flow attempts to create a role with this reserved name when it already exists.

Common situations: Automated cleanup script tries to remove all roles including system ones; a migration or reset tooling attempts to delete and recreate admin roles; an admin mistakenly attempts to delete the built-in admin role via the console UI or API.

Related errors


AI-assisted analysis of alibaba/nacos@9b989acdf1 (2026-08-14). Data as JSON: /api/errors/4e475d6a14738e73. Report an issue: GitHub.

Appendix: source

Thrown at plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/AbstractCheckedRoleService.java:148

        this.hasGlobalAdminRole = hasGlobalAdminRole;
        return hasGlobalAdminRole;
    }
    
    /**
     * Mark the local global-admin lookup cache after an administrator role is created.
     */
    protected void markGlobalAdminRolePresent() {
        hasGlobalAdminRole = true;
    }
    
    /**
     * Reject deletion or manual creation of system-reserved roles.
     *
     * @param role role name to check
     */
    protected void rejectReservedRole(String role) {
        if (AuthConstants.GLOBAL_ADMIN_ROLE.equals(role)) {
            throw new IllegalArgumentException(
                "role '" + AuthConstants.GLOBAL_ADMIN_ROLE + "' is not permitted to delete!");
        }
        if (AuthConstants.ANONYMOUS_ROLE.equals(role)) {
            throw new IllegalArgumentException(
                "role '" + AuthConstants.ANONYMOUS_ROLE + "' is reserved by the system");
        }
    }
    
    /**
     * If API is update user password, don't do permission check, because there is permission check in API logic.
     */
    private boolean isUpdatePasswordPermission(Permission permission) {
        Properties properties = permission.getResource().getProperties();
        return null != properties && properties.contains(AuthConstants.UPDATE_PASSWORD_ENTRY_POINT);
    }
    
    private String joinResource(Resource resource) {
        if (SignType.SPECIFIED.equals(resource.getType())) {

View on GitHub (pinned to 9b989acdf1)