Skip to main content
Blog|
Learning center

HTTP 409 Conflict: what it means and how to fix it

|
Apr 9, 2026|10 min read
LEARNING CENTERHTTP 409 Conflict: what itmeans and how to fix itHOSTNEYhostney.comApril 9, 2026

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:

  1. Parse the response body for the conflict reason
  2. Decide whether the conflict is recoverable
  3. If recoverable, fetch the current state, adjust, and retry
  4. 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 codeMeaningThe problem is…
400 Bad RequestMalformed requestthe request syntax or content
401 UnauthorizedMissing or invalid credentialsauthentication
403 ForbiddenNot allowedauthorization
404 Not FoundResource does not existthe target URL
405 Method Not AllowedWrong HTTP methodthe request method
409 ConflictConflicts with current statethe resource state
429 Too Many RequestsRate limitedrequest 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.