apache/druid · error · BasicSecurityDBResourceException

Group mapping [%s] already has role [%s].

Error message

Group mapping [%s] already has role [%s].

What it means

Thrown when assigning a role to a group mapping that already contains that role. Druid treats the assignment as already satisfied and throws BasicSecurityDBResourceException instead of allowing a duplicate entry in the mapping's role set.

Source

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

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

    byte[] oldGroupMappingMapValue = getCurrentGroupMappingMapBytes(prefix);
    Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
        objectMapper,
        oldGroupMappingMapValue
    );
    BasicAuthorizerGroupMapping groupMapping = groupMappingMap.get(groupMappingName);
    if (groupMappingMap.get(groupMappingName) == null) {
      throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
    }

    if (groupMapping.getRoles().contains(roleName)) {
      throw new BasicSecurityDBResourceException("Group mapping [%s] already has role [%s].", groupMappingName, roleName);
    }

    groupMapping.getRoles().add(roleName);
    byte[] newGroupMapValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);

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

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

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Check the mapping's current roles via GET .../groupMappings/<name> and skip the assignment if the role is present
  2. Treat the 400 'already has role' response as success in idempotent provisioning flows
  3. Deduplicate the role list in automation config before applying
  4. Add locking/leader election if multiple jobs assign roles concurrently

Example fix

// before
client.post("/druid-ext/basic-security/authorization/db/v1/internal-auth/groupMappings/ldap-admins/roles/admin");
// after (idempotent)
GroupMapping gm = client.getGroupMapping("internal-auth", "ldap-admins");
if (!gm.getRoles().contains("admin")) {
  client.post("/druid-ext/basic-security/authorization/db/v1/internal-auth/groupMappings/ldap-admins/roles/admin");
}
Defensive patterns

Strategy: try-catch

Validate before calling

// skip assignment if already present
GroupMapping gm = getGroupMapping(authorizer, mappingName);
if (gm != null && gm.getRoles().contains(roleName)) return;

Type guard

boolean hasRole(GroupMapping m, String role) { return m != null && m.getRoles() != null && m.getRoles().contains(role); }

Try / catch

try {
  assignRoleToGroupMapping(authorizer, mappingName, roleName);
} catch (BasicSecurityDBResourceException e) {
  if (e.getMessage().contains("already has role")) { /* idempotent success */ }
  else throw e;
}

Prevention

When it happens

Trigger: POST to /groupMappings/<name>/roles/<role> when the mapping's roles list already includes the role; re-running a provisioning script without idempotency checks; two admins or jobs performing the same assignment concurrently.

Common situations: Idempotent infrastructure-as-code re-apply hitting an already-applied state; retry logic resending a request that actually succeeded the first time; duplicate entries in automation config applied in a loop.

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 apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/6fcce67ba13f5a6b. Report an issue: GitHub.