apache/druid · error · BasicSecurityDBResourceException
User [%s] does not have role [%s].
Error message
User [%s] does not have role [%s].
What it means
Thrown by the coordinator basic authorizer metadata storage updater when unassigning a role from a user: the user exists in the authorizer's metadata store but its role set does not contain the requested role. Druid validates the user-role association before writing the updated user map, so the delete fails fast with BasicSecurityDBResourceException instead of silently no-oping.
Source
Thrown at extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java:1036
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] does not have role [%s].", userName, roleName);
}
user.getRoles().remove(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 assignGroupMappingRoleOnce(String prefix, String groupMappingName, String roleName)
{
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,View on GitHub (pinned to 9b90983fd2)
Solutions
- Verify the user's current roles via GET /druid-ext/basic-security/authorization/db/v1/<authorizerName>/users/<userName> before unassigning
- Treat this 400 response as an idempotent success if the goal is 'user must not have role' and skip retrying
- Re-fetch the user list to refresh stale caches before retrying
- Check for concurrent admin/API callers modifying the same user
Example fix
// before (blind delete)
client.delete("/druid-ext/basic-security/authorization/db/v1/internal-auth/users/alice/roles/readonly");
// after (check first)
BasicAuthorizerUser user = client.getUser("internal-auth", "alice");
if (user.getRoles().contains("readonly")) {
client.delete("/druid-ext/basic-security/authorization/db/v1/internal-auth/users/alice/roles/readonly");
} Defensive patterns
Strategy: try-catch
Validate before calling
// check assignment before deleting Set<String> roles = getUser(authorizer, userName).getRoles(); if (!roles.contains(roleName)) return; // nothing to revoke
Type guard
boolean isAssigned(BasicAuthorizerUser u, String role) { return u != null && u.getRoles() != null && u.getRoles().contains(role); } Try / catch
try {
unassignRole(authorizer, userName, roleName);
} catch (BasicSecurityDBResourceException e) {
if (e.getMessage().contains("does not have role")) { /* already revoked: treat as success */ }
else throw e;
} Prevention
- Read the user's roles before mutating them
- Make revoke operations idempotent by treating 'does not have role' as success
- Avoid concurrent admin edits to the same user
- Use exact role names from a listing, not hand-typed strings
When it happens
Trigger: Calling the coordinator security API DELETE to unassign a role (unassignRoleFromUser path) with a (userName, roleName) pair where the user was never assigned that role; racing with another admin who already removed the role from the user; stale client-side state from a cached user listing.
Common situations: Automation scripts that revoke roles idempotently without first checking the user's current roles; cleanup tooling deleting role assignments that were already removed; concurrent admin operations through the Druid console and API.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Group mapping [%s] already has role [%s].
- Group mapping [%s] does not have role [%s].
- User [%s] does not exist.
- User [%s] does not exist.
- Could not create user [%s] due to concurrent update contenti
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/43f7760c93c70356.
Report an issue: GitHub.