Skip to content

409 Conflict

What is HTTP 409 Conflict?

409 Conflict
Imagine you're signing up for a game and you want the username "CoolGamer123." B...
HTTP 409 Conflict status code illustration

Explain Like I’m 3

Imagine there’s one cookie left on the plate. You and your friend both reach for it at the same time! You both want it, but there’s only one cookie. Mom says “Sorry, you both can’t have it - there’s a conflict!” That’s what 409 Conflict means - two people want to do something with the same thing at the same time, and it causes a problem!

Example: You want to name your new toy bear “Mr. Fluffy,” but you already have another toy with that name! You can’t have two toys with the same name - that’s a conflict!

Explain Like I’m 5

Imagine you’re signing up for a game and you want the username “CoolGamer123.” But someone else already took that username yesterday! The game says “Sorry, that username is already taken - conflict!” You can see the signup form, everything looks right, but you can’t use that name because it conflicts with something that already exists. That’s a 409 Conflict - your request is valid, but it bumps into something that’s already there.

Example: Two people editing the same Google Doc at the same time, trying to change the same sentence. Google has to figure out which version to keep because both changes conflict with each other!

Jr. Developer

409 Conflict indicates that your request is valid and well-formed, but it can’t be completed because it conflicts with the current state of the server. This is different from validation errors (422) - the data is valid, but the operation conflicts with existing resources or state.

Common scenarios for 409:

  • Duplicate resource creation: Trying to create a user with an email that already exists
  • Concurrent modifications: Two clients updating the same resource simultaneously (race condition)
  • Version conflicts: Your changes are based on an outdated version of the resource
  • State transition conflicts: Trying to delete a resource that other resources depend on
  • Business logic conflicts: Attempting an operation that conflicts with business rules

Key distinction from similar codes:

  • 422 Unprocessable Content: Data fails validation (invalid format)
  • 409 Conflict: Data is valid but conflicts with current state
  • 412 Precondition Failed: Conditional request failed (If-Match, If-None-Match)
  • 423 Locked: Resource is locked by another process

Code Example

// Express.js example: Handling duplicate resource creation
const express = require('express');
const app = express();
app.post('/api/users', async (req, res) => {
const { email, username } = req.body;
// Check if email already exists
const existingEmail = await db.users.findOne({ email });
if (existingEmail) {
return res.status(409).json({
error: 'Conflict',
message: 'Email address already registered',
field: 'email',
conflict_type: 'DUPLICATE_RESOURCE'
});
}
// Check if username already exists
const existingUsername = await db.users.findOne({ username });
if (existingUsername) {
return res.status(409).json({
error: 'Conflict',
message: 'Username already taken',
field: 'username',
conflict_type: 'DUPLICATE_RESOURCE'
});
}
// Create user
const user = await db.users.create({ email, username });
res.status(201).json(user);
});
// Concurrent update conflict (race condition)
app.put('/api/posts/:id', async (req, res) => {
const post = await db.posts.findById(req.params.id);
if (!post) {
return res.status(404).json({ error: 'Post not found' });
}
// Check version to detect concurrent modifications
if (req.body.version !== post.version) {
return res.status(409).json({
error: 'Conflict',
message: 'Post was modified by another user',
conflict_type: 'VERSION_CONFLICT',
current_version: post.version,
your_version: req.body.version
});
}
// Update with new version number
post.title = req.body.title;
post.content = req.body.content;
post.version += 1;
await post.save();
res.json(post);
});

Crash Course

HTTP 409 Conflict (RFC 9110 §15.5.10) indicates the request could not be completed due to a conflict with the current state of the target resource. The conflict is expected to be resolvable by the client, typically by modifying the request or waiting for state changes.

RFC 9110 Definition: “The 409 (Conflict) status code indicates that the request could not be completed due to a conflict with the current state of the target resource. This code is used in situations where the user might be able to resolve the conflict and resubmit the request.”

Common Conflict Types:

  1. Duplicate Resource Conflicts

    • Creating resource with duplicate unique identifier
    • Username/email already exists
    • Database unique constraint violations
  2. Concurrent Modification Conflicts

    • Two clients updating same resource simultaneously
    • Lost update problem
    • Race conditions in distributed systems
  3. Version Conflicts

    • Client’s version is stale (outdated)
    • Changes based on old data
    • Optimistic locking failures
  4. State Transition Conflicts

    • Invalid state machine transitions
    • Deleting resources with dependencies
    • Business process violations
  5. Resource Dependency Conflicts

    • Circular dependencies
    • Foreign key violations on deletion
    • Referential integrity conflicts

Conflict Resolution Strategies:

  1. Optimistic Locking: Include version number with each update
  2. ETags: Use HTTP ETags with If-Match headers (returns 412, not 409)
  3. Timestamps: Compare last-modified timestamps
  4. Retry Logic: Client retries after fetching latest state
  5. Merge Strategies: Three-way merge for complex data

Response Format Best Practices:

  • Clearly identify conflict type
  • Provide current server state if safe to disclose
  • Suggest resolution steps
  • Include conflicting field/resource identifiers

Code Example

// Optimistic locking with version numbers
const express = require('express');
const app = express();
// Schema includes version field
const postSchema = {
title: String,
content: String,
version: { type: Number, default: 1 },
updated_at: Date
};
// GET returns current version
app.get('/api/posts/:id', async (req, res) => {
const post = await db.posts.findById(req.params.id);
res.json({
id: post.id,
title: post.title,
content: post.content,
version: post.version, // Client must track this
updated_at: post.updated_at
});
});
// PUT requires matching version
app.put('/api/posts/:id', async (req, res) => {
const { title, content, version } = req.body;
const post = await db.posts.findById(req.params.id);
if (!post) {
return res.status(404).json({ error: 'Not Found' });
}
// Version mismatch = conflict
if (version !== post.version) {
return res.status(409).json({
error: 'Conflict',
message: 'Resource was modified by another client',
conflict_type: 'VERSION_MISMATCH',
client_version: version,
server_version: post.version,
current_state: {
title: post.title,
content: post.content,
updated_at: post.updated_at
},
resolution: 'Fetch latest version and retry'
});
}
// Atomic update with version increment
const updated = await db.posts.findOneAndUpdate(
{ _id: req.params.id, version: version },
{
$set: { title, content, updated_at: new Date() },
$inc: { version: 1 }
},
{ new: true }
);
if (!updated) {
// Race condition: version changed between read and update
return res.status(409).json({
error: 'Conflict',
message: 'Resource was modified during update',
conflict_type: 'RACE_CONDITION'
});
}
res.json(updated);
});
// Handling dependent resource deletion
app.delete('/api/projects/:id', async (req, res) => {
const project = await db.projects.findById(req.params.id);
if (!project) {
return res.status(404).json({ error: 'Not Found' });
}
// Check for dependent tasks
const taskCount = await db.tasks.countDocuments({
project_id: req.params.id
});
if (taskCount > 0) {
return res.status(409).json({
error: 'Conflict',
message: 'Cannot delete project with existing tasks',
conflict_type: 'DEPENDENT_RESOURCES',
dependent_count: taskCount,
resolution: 'Delete all tasks first or use cascade delet...

Deep Dive

HTTP 409 Conflict represents a fundamental challenge in distributed systems: managing concurrent access to shared resources. While RFC 9110 defines 409 broadly as conflicts with “current state,” practical implementations must handle various conflict types with different resolution strategies.

Theoretical Foundation:

409 Conflict addresses violations of resource invariants - conditions that must always be true about a resource. When a client’s request would violate an invariant (uniqueness, referential integrity, state machine rules), the server returns 409 to signal the conflict.

Conflict Taxonomy:

  1. Uniqueness Conflicts (Duplicate Resources)

    • Violations of unique constraints
    • Primary key duplicates
    • Natural key collisions (email, username)
    • Resolution: Choose different identifier
  2. Concurrency Conflicts (Lost Update Problem)

    • Multiple clients modifying same resource
    • Classic database concurrency issue
    • Resolution: Optimistic locking, pessimistic locking, CRDTs
  3. Version Conflicts (Stale Data)

    • Client operating on outdated resource version
    • Divergent update problem
    • Resolution: Fetch latest, merge changes, retry
  4. State Conflicts (Invalid Transitions)

    • Request violates state machine rules
    • Business process constraints
    • Resolution: Wait for state change or modify request
  5. Dependency Conflicts (Referential Integrity)

    • Deleting referenced resources
    • Circular dependencies
    • Resolution: Cascade operations or remove dependencies

409 vs 412 vs 422 - Technical Distinctions:

  • 412 Precondition Failed: Conditional request headers failed (If-Match, If-Unmodified-Since) - RFC 9110 recommends this for ETag mismatches
  • 409 Conflict: General state conflicts, duplicate resources, concurrent modifications
  • 422 Unprocessable Content: Validation failures, semantic errors, invalid data format

Note: There’s debate in the community about using 409 vs 412 for optimistic locking. RFC 9110 technically recommends 412 for If-Match failures, but many APIs use 409 for version conflicts detected via application-level version fields.

Concurrency Control Strategies:

  1. Optimistic Locking

    • Assume conflicts are rare
    • Check version at update time
    • Return 409 if version mismatch
    • Low overhead, works well for low-contention resources
  2. Pessimistic Locking

    • Lock resource before modification
    • Use 423 Locked while editing
    • Preve…

Code Example

// Production-grade optimistic locking with comprehensive conflict handling
const { createHash } = require('crypto');
const Redis = require('ioredis');
const redis = new Redis();
// Advanced version-based optimistic locking with retry logic
class OptimisticLockService {
constructor(db) {
this.db = db;
this.maxRetries = 3;
this.retryDelay = 100; // ms
}
// Update with optimistic locking and automatic retry
async updateWithRetry(collection, id, updates, options = {}) {
const { maxRetries = this.maxRetries, onConflict } = options;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await this.updateOnce(collection, id, updates);
} catch (err) {
if (err.statusCode === 409 && attempt < maxRetries) {
// Conflict detected, retry with exponential backoff
const delay = this.retryDelay * Math.pow(2, attempt);
await this.sleep(delay);
// Optional conflict callback
if (onConflict) {
await onConflict(attempt, err);
}
continue;
}
throw err;
}
}
throw new Error(`Failed after ${maxRetries} retries`);
}
async updateOnce(collection, id, updates) {
// Fetch current version
const current = await this.db[collection].findById(id);
if (!current) {
const err = new Error('Resource not found');
err.statusCode = 404;
throw err;
}
// Allow updates to be a function of current state
const newData = typeof updates === 'function'
? updates(current)
: updates;
// Atomic update with version check
const result = await this.db[collection].findOneAndUpdate(
{ _id: id, version: current.version },
{
$set: { ...newData, updated_at: new Date() },
$inc: { version: 1 }
},
{ new: true, runValidators: true }
);
if (!result) {
// Version changed between read and update
const err = new Error('Resource was modified by another client');
err.statusCode = 409;
err.conflictType = 'VERSION_MISMATCH';
throw err;
}
return result;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
const lockService = new OptimisticLockService(db);
// Express endpoint using optimistic locking service
app.put('/api/documents/:id', async (req, res) => {
try {
const updated = await lockService.updateWithRetry(
'documents',
req.params.id,
current => ({
// Update based on current state
title: req.body.title,
content: req.body.content,
word_count: req.body.content.split(/\s+/).length,
// Preserve fields client shouldn't modify
created_at: current.created_at,
author_id: current.author_id
}),
{
maxRetries: 3,
onConflict: async (attempt, err) => {
console.log(`Conflict on attempt ${a...

Frequently Asked Questions

What's the difference between 409 Conflict and 422 Unprocessable Content?

422 is for validation failures - the data format/values are invalid (wrong email format, age out of range). 409 is for state conflicts - the data is valid but conflicts with current server state (email already registered, concurrent update detected). Ask: 'Is the data invalid?' → 422. 'Does it conflict with existing state?' → 409.

Should I use 409 or 412 for optimistic locking failures?

RFC 9110 technically recommends 412 Precondition Failed for If-Match/ETag mismatches. However, many APIs use 409 for application-level version conflicts (version number fields). Use 412 when using HTTP conditional headers (If-Match, If-Unmodified-Since), and 409 for custom version tracking in request body. Both are widely accepted in practice.

How should clients handle 409 responses?

The conflict is usually resolvable by: (1) Fetching the latest resource state (GET request), (2) Reviewing the conflict details in the error response, (3) Modifying the request to resolve the conflict, (4) Retrying with updated data. For duplicate resources, choose a different identifier. For version conflicts, merge changes with current state. Don't blindly retry without resolving the conflict.

What should I include in a 409 error response?

Include: (1) Conflict type (DUPLICATE_RESOURCE, VERSION_MISMATCH, etc.), (2) Which field/resource is conflicting, (3) Current server state if safe to disclose, (4) Suggested resolution steps. For version conflicts, include both client's version and server's current version. For duplicates, specify which field is duplicated (username, email, etc.).

Can 409 conflicts be prevented?

Strategies to minimize conflicts: (1) Use optimistic locking with version numbers, (2) Implement unique constraints in database, (3) Use distributed locks for high-contention resources, (4) Design conflict-free APIs where possible (append-only operations), (5) For duplicates, check existence before creation (though race conditions still possible). Complete prevention is impossible in distributed systems due to concurrent access.

Should I retry requests that return 409?

It depends on the conflict type. For version conflicts, yes - fetch latest state, merge changes, and retry. For duplicate resource creation, no - choose a different identifier instead. For state conflicts, depends on whether the state might change (e.g., wait for locked resource to unlock). Implement exponential backoff and max retry limits. Never retry without resolving the underlying conflict.

How do I handle 409 in distributed systems with eventual consistency?

Eventual consistency complicates conflict handling because replicas may diverge. Strategies: (1) Use CRDTs (Conflict-Free Replicated Data Types) for automatic conflict resolution, (2) Implement application-level conflict resolution (last-write-wins, custom merge logic), (3) Use strong consistency for critical operations, (4) Accept that some operations may fail and require manual resolution. Design your data model to minimize conflicts.

Common Causes

  • Attempting to create resource with duplicate unique identifier (email, username)
  • Concurrent modification by multiple clients (race condition)
  • Client’s resource version is outdated (stale data)
  • Trying to delete resource that other resources depend on
  • Invalid state machine transition (canceling completed order)
  • Violating database unique constraints
  • Circular dependencies in resource relationships
  • Business logic conflicts (booking already-reserved time slot)
  • Attempting to create resource that already exists (idempotency violation)
  • File upload version conflict (newer version exists on server)
  • Inventory conflicts (multiple users purchasing last item)
  • Pessimistic lock held by another client (use 423 Locked instead)

Implementation Guidance

  • For duplicate resources: Choose a different unique identifier (username, email)
  • For version conflicts: Fetch latest resource state (GET), merge your changes, retry
  • For concurrent updates: Implement optimistic locking with version numbers
  • For state conflicts: Verify resource is in correct state before operation
  • For dependency conflicts: Remove or cascade-delete dependent resources first
  • Use If-Match header with ETags for HTTP-level concurrency control
  • Implement retry logic with exponential backoff for transient conflicts
  • Review error response for conflict type and resolution suggestions
  • For high-contention resources: Consider pessimistic locking or queue-based processing
  • Check for race conditions: use atomic database operations
  • For distributed systems: Implement conflict resolution strategy (CRDT, LWW, merge)
  • Monitor 409 rate: high rates indicate concurrency hotspots needing architectural changes

Comments