apache/druid · warning · BasicSecurityDBResourceException

User [%s] already has role [%s].

Error message

User [%s] already has role [%s].

What it means

BasicSecurityDBResourceException thrown when assigning a role the user already holds. The coordinator checks the user's current role set before mutating it, rejecting redundant assignments so the role list stays a clean set. Purely informational in effect: the desired end state already exists.

Source

Thrown at extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java:1000

        objectMapper,
        oldRoleMapValue
    );
    if (roleMap.get(roleName) == null) {
      throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
    }

    byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);
    Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
        objectMapper,
        oldUserMapValue
    );
    BasicAuthorizerUser user = userMap.get(userName);
    if (userMap.get(userName) == null) {
      throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
    }

    if (user.getRoles().contains(roleName)) {
      throw new BasicSecurityDBResourceException("User [%s] already has role [%s].", userName, roleName);
    }

    user.getRoles().add(roleName);
    byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);

    // Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
    return tryUpdateUserAndRoleMap(
        prefix,
        userMap, oldUserMapValue, newUserMapValue,
        roleMap, oldRoleMapValue, oldRoleMapValue
    );
  }

  private boolean unassignUserRoleOnce(String prefix, String userName, String roleName)
  {
    byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
    Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
        objectMapper,

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Check the user's current roles via GET .../users/<userName> and skip assignment if present
  2. Treat 'already has role' responses as success in idempotent automation
  3. Track assignment state in your provisioning tool to avoid re-issuing assignments
  4. Remove the role first (if a reset is intended) before re-assigning

Example fix

// before
client.assignRole("default", "alice", "readRole"); // fails on rerun
// after
if (!userRoles("default", "alice").contains("readRole")) {
  client.assignRole("default", "alice", "readRole");
}
Defensive patterns

Strategy: validation

Validate before calling

java.util.Set<String> roles = getUser("default", userName).getRoles();
if (roles.contains(roleName)) skipAssignment(userName, roleName);

Try / catch

try {
  client.assignRole("default", userName, roleName);
} catch (BasicSecurityDBResourceException e) {
  if (e.getMessage().contains("already has role")) {
    log.info("{} already has {}", userName, roleName); // treat as success
  } else { throw e; }
}

Prevention

When it happens

Trigger: POST .../users/<userName>/roles with roleName already present in the user's roles (e.g. from a previous run of the same script).

Common situations: Non-idempotent provisioning scripts run repeatedly; concurrent admins assigning the same role; retry logic that re-sends an assignment that already succeeded; bulk imports that assume empty role sets.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/2be51e66547cea6f. Report an issue: GitHub.