A 409 Conflict response means the server understood your request but cannot process it because it conflicts with the current state of the resource. The request itself is valid – the syntax is correct, the authentication checks out, and the server knows what you are asking for. But completing the request would create an inconsistency, so the server refuses.
This is different from a 400 Bad Request, which means the request itself is malformed, or a 403 Forbidden, which means you do not have permission. A 409 means your request is well-formed and authorized – it just cannot be applied to the resource in its current state.
What the HTTP specification says#
HTTP 409 is defined in RFC 9110, Section 15.5.10. The specification says the request could not be completed due to a conflict with the current state of the target resource. The server should include enough information in the response body for the client to recognize the source of the conflict and resolve it.
The specification also notes that 409 is most likely to occur with PUT requests, where the client is trying to update a resource and the update conflicts with a prior change. But it applies to any HTTP method where the current resource state prevents the operation from completing.
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "conflict",
"message": "This email address is already registered to another account."
}
The key distinction: a 409 implies the conflict is resolvable. The client can modify the request and try again after addressing whatever caused the conflict. If the conflict is not resolvable, a different status code is more appropriate.
Common causes of a 409#
Duplicate resource creation
The most common cause in web applications. You try to create a resource that already exists – a user account with an email that is already registered, a domain name that is already added, or a database record with a unique constraint violation.
The application could return a generic 400 for this, but a 409 is more precise. It tells the client exactly what happened: the resource you are trying to create conflicts with one that already exists.
POST /api/v2/users
Content-Type: application/json
{
"email": "user@example.com",
"name": "John"
}
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "conflict",
"message": "A user with this email address already exists."
}
Concurrent edit conflicts
Two users editing the same resource at the same time. User A loads a page, User B loads the same page, User B saves their changes, then User A tries to save. User A’s save would overwrite User B’s changes because User A’s edit was based on the old version.
This is the classic optimistic concurrency control scenario. The server tracks a version number or timestamp for the resource. When User A tries to save, the server compares the version in the request with the current version. If they do not match, someone else changed the resource in between, and the server returns a 409.
PUT /api/v2/posts/42
Content-Type: application/json
If-Match: "v3"
{
"title": "Updated title",
"content": "Updated content"
}
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "conflict",
"message": "This resource was modified by another user. Reload and try again.",
"current_version": "v4"
}
The
If-Match
header carries the version the client last saw. The server’s current version is
v4
, so the edit is rejected. The client should fetch the latest version and either merge the changes or ask the user to resolve the conflict.
State transition conflicts
Some operations are only valid when a resource is in a specific state. Trying to cancel an order that has already shipped. Trying to publish a post that has already been published. Trying to delete a user account that has active subscriptions.
POST /api/v2/orders/99/cancel
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "conflict",
"message": "This order has already shipped and cannot be cancelled.",
"current_status": "shipped"
}
The request is valid in general – cancelling an order is a legitimate action. But this specific order is in a state that does not allow cancellation.
Database constraint violations
At the database level, a 409 often maps to a unique constraint violation, a foreign key conflict, or a check constraint failure. The application tries to insert or update a row that violates a database rule.
For example, in WordPress, trying to create two posts with the same slug will hit a unique constraint. The application should catch this and return a 409 with a message explaining the conflict rather than letting the database error bubble up as a 500 Internal Server Error.
Version control and deployment conflicts
Git-based deployment systems and CI/CD pipelines can return 409 when you try to push changes that conflict with the current state. GitHub’s API returns a 409 when you try to merge a pull request that has merge conflicts, or when you try to create a branch that already exists.
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"message": "Merge conflict",
"documentation_url": "https://docs.github.com/rest/pulls/pulls#merge-a-pull-request"
}
DNS and domain conflicts
Hosting platforms return a 409 when you try to add a domain that is already assigned to another account or site. The domain exists in the system, and adding it again would create a conflict.
Similarly, trying to create a DNS record that conflicts with an existing one – like adding a CNAME to a name that already has an A record – produces a conflict.
How to diagnose a 409#
Read the response body
Unlike many HTTP errors, a 409 almost always includes a useful error message. The server is required to provide enough information for the client to understand and resolve the conflict. Check the response body in your browser’s developer tools (F12, Network tab) or with curl:
curl -v -X POST https://api.example.com/resource \
-H "Content-Type: application/json" \
-d '{"name": "test"}'
The
-v
flag shows the full response including headers and body.
Check the current resource state
If the error message mentions a conflict with the current state, fetch the resource to see what state it is in:
curl https://api.example.com/resource/42
Compare the current state with what your request expects. If you are updating a resource, the version may have changed since you last fetched it.
Check for duplicate entries
If you are creating a resource and getting a 409, the resource likely already exists. Search for it:
- In a web application, check whether the email, username, domain, or other unique field is already in use
- In an API, try a GET request for the resource you are trying to create
- In a database, check the unique constraints on the table
Check the timing
If 409 errors appear intermittently, they may be caused by concurrent requests. Two users submitting the same form, a double-click sending duplicate requests, or a retry mechanism that fires while the original request is still processing.
Browser developer tools show the timing of requests. Look for two requests to the same endpoint that overlap in time.
How to fix a 409#
For duplicate resource errors
The fix depends on the intent:
If you are trying to create something new and it already exists, you need to use a different unique value (a different email, a different slug, a different domain name) or update the existing resource instead of creating a new one.
If you are retrying a request that may have already succeeded (network timeout, page reload), check whether the resource was created by the first request before sending another create request. Well-designed APIs use idempotency keys to handle this – the client sends a unique key with the request, and the server returns the existing resource if it was already created with that key.
For concurrent edit conflicts
Fetch the latest version of the resource, merge your changes with the current state, and submit again. In a web application, this usually means reloading the page and re-applying your edits.
If the application supports it, use optimistic locking headers:
PUT /api/v2/posts/42
If-Match: "v4"
This tells the server to only apply the update if the version matches. If it does not, you get a 409 and can handle it gracefully.
For state transition conflicts
Check what state the resource is in and whether your intended action is valid for that state. If an order has shipped and cannot be cancelled, you may need to initiate a return instead. If a post is already published, you may need to update it rather than publish it again.
The response body usually tells you the current state. Adjust your action based on that state.
For API integrations
If you are building an integration that hits 409 errors, your code should:
- Parse the response body for the conflict reason
- Decide whether the conflict is recoverable
- If recoverable, fetch the current state, adjust, and retry
- If not recoverable, surface the error message to the user
try {
const response = await axios.post('/api/resource', data);
} catch (error) {
if (error.response?.status === 409) {
// Conflict - fetch current state and retry or notify user
const current = await axios.get(`/api/resource/${id}`);
// Merge or prompt user
}
}
For WordPress
WordPress itself rarely returns a 409 to the browser. But WordPress REST API clients and plugins that interact with external APIs may encounter them.
If you see a 409 in the context of WordPress:
- Gutenberg editor – the block editor uses the REST API to save posts. A 409 can occur if two browser tabs are editing the same post and both try to save. The updating failed or publishing failed error can sometimes be traced to a conflict like this.
- Plugin API calls – plugins that sync data with external services (payment gateways, email providers, CRMs) may receive 409 responses when the external service detects a duplicate or conflict. Check the plugin’s logs for the full error message.
- WooCommerce – order status transitions can produce conflicts if two processes try to update the same order simultaneously. This is more common on high-traffic stores where webhook handlers and admin actions overlap.
409 vs other 4xx errors#
| Status code | Meaning | The problem is… |
|---|---|---|
| 400 Bad Request | Malformed request | the request syntax or content |
| 401 Unauthorized | Missing or invalid credentials | authentication |
| 403 Forbidden | Not allowed | authorization |
| 404 Not Found | Resource does not exist | the target URL |
| 405 Method Not Allowed | Wrong HTTP method | the request method |
| 409 Conflict | Conflicts with current state | the resource state |
| 429 Too Many Requests | Rate limited | request frequency |
The key difference between 409 and 400: a 400 means the request is invalid regardless of server state – bad syntax, missing required fields, wrong data types. A 409 means the request is perfectly valid but cannot be applied because of what is already on the server. Sending the exact same request at a different time (after resolving the conflict) could succeed.
The key difference between 409 and 422 (Unprocessable Entity): a 422 means the request is syntactically correct but semantically invalid – the server understands what you are asking but the data does not make sense (like a negative quantity). A 409 means the data makes sense, but it conflicts with existing data.
Preventing 409 errors in your application#
Use idempotency keys
For create operations, accept a client-generated idempotency key. If the client retries with the same key, return the existing resource instead of a 409. This prevents duplicate creation from network retries, double-clicks, and browser refreshes.
Implement optimistic locking
For update operations, include a version field (ETag, timestamp, or version number) in your resources. Require clients to send the version with their update request. If the version does not match, return a 409 with the current version so the client can merge and retry.
Validate before writing
Check for conflicts before attempting the database write. This produces better error messages and avoids relying on database constraint violations for flow control:
// Check for existing email before inserting
const existing = await db.query('SELECT id FROM users WHERE email = ?', [email]);
if (existing.length > 0) {
return res.status(409).json({
resource: "User",
message: "A user with this email address already exists."
});
}
Handle race conditions
For operations where concurrent access is expected, use database-level locks or atomic operations. A SELECT-then-INSERT pattern has a race window where two requests can both pass the check and then both try to insert. Use INSERT … ON DUPLICATE KEY or database transactions with proper isolation levels to handle this correctly.
Summary#
A 409 Conflict means your request is valid but conflicts with the current state of the resource on the server. The most common causes are duplicate creation attempts, concurrent edit conflicts, and invalid state transitions. Unlike most HTTP errors, a 409 is designed to be resolvable – the response body tells you what the conflict is, and you can fix it by adjusting your request.
For other HTTP error codes, see HTTP 500 Internal Server Error for server-side failures, 401 Unauthorized for authentication errors, and HTTP 2xx success codes for when everything works as expected.