bitwarden/server · error · ConflictException
Conflict.
Error message
Conflict.
What it means
Thrown as a ConflictException (HTTP 409) with the default message 'Conflict.' by PostGroupCommand.PostGroupAsync when creating a group whose externalId already matches an existing group in the same organization. Unlike the patch path, this uses exact string comparison (==) rather than case-insensitive. The default ConflictException constructor supplies the message 'Conflict.'.
Source
Thrown at bitwarden_license/src/Scim/Groups/PostGroupCommand.cs:34
public PostGroupCommand(
IGroupRepository groupRepository,
ICreateGroupCommand createGroupCommand)
{
_groupRepository = groupRepository;
_createGroupCommand = createGroupCommand;
}
public async Task<Group> PostGroupAsync(Organization organization, ScimGroupRequestModel model)
{
if (string.IsNullOrWhiteSpace(model.DisplayName))
{
throw new BadRequestException();
}
var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id);
if (!string.IsNullOrWhiteSpace(model.ExternalId) && groups.Any(g => g.ExternalId == model.ExternalId))
{
throw new ConflictException();
}
var group = model.ToGroup(organization.Id);
await _createGroupCommand.CreateGroupAsync(group, organization, EventSystemUser.SCIM, collections: null);
await UpdateGroupMembersAsync(group, model);
return group;
}
private async Task UpdateGroupMembersAsync(Group group, ScimGroupRequestModel model)
{
if (model.Members == null)
{
return;
}
var memberIds = new List<Guid>();
foreach (var id in model.Members.Select(i => i.Value))View on GitHub (pinned to e93b962371)
Solutions
- Check if the group already exists: GET /v2/{organizationId}/Groups?filter=externalId eq "...".
- If it exists, use PUT or PATCH to update it instead of POSTing a new one.
- Deduplicate externalIds in the source directory before provisioning.
- Configure the IdP to treat 409 as 'already exists' and reconcile rather than retry-create.
Defensive patterns
Strategy: validation
Validate before calling
// Before creating, check if a group with this externalId exists
var existing = await scimClient.ListGroupsAsync(orgId, filter: $"externalId eq \"{externalId}\"");
if (existing.Any()) { /* use PUT/PATCH on existing instead */ } Try / catch
try { await scimClient.CreateGroupAsync(orgId, model); }
catch (ScimException ex) when (ex.StatusCode == 409)
{ // group likely exists already — fetch and update instead
var existing = await scimClient.ListGroupsAsync(orgId, filter: $"externalId eq \"{model.ExternalId}\"");
if (existing.Any()) await scimClient.PutGroupAsync(orgId, existing.First().Id, model); } Prevention
- Implement idempotent SCIM creation: on 409, fall back to lookup-and-update.
- Deduplicate externalIds in the source directory.
- Avoid retrying POST on 409 — switch to PUT/PATCH.
When it happens
Trigger: POST /v2/{organizationId}/Groups with a non-empty externalId that matches an existing group's externalId in the same org. Common when the IdP retries a creation that partially succeeded, or when two groups in the directory share the same externalId.
Common situations: IdP re-provisioning after a timeout where the first POST succeeded but the response was lost. Duplicate externalIds in the source directory. Directory connector re-sync without deduplication.
Related errors
- ExternalId already exists for another group.
- User already exists.
- ExternalId already exists for another user.
- ExternalId cannot exceed 300 characters.
- Organization already belongs to a provider.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/cfa97dc4ab0bba06.
Report an issue: GitHub.