HTTP 409 Conflict

Your request conflicts with the current state of the resource on the server. This typically means you are trying to create something that already exists, update a resource that was modified by someone else since you last read it, or perform an operation that violates a business rule.

What Is HTTP 409 Conflict?

HTTP 409 Conflict is a client error status code indicating the request could not be completed because it conflicts with the current state of the target resource. Unlike 400 Bad Request, which indicates the request syntax is invalid, 409 means the request is well-formed but cannot be applied because of a logical conflict with the resource's current state. The server should include enough information in the response body for the client to understand and resolve the conflict.

The 409 status code is essential for maintaining data integrity in applications with concurrent users. The most common pattern is optimistic concurrency control, where clients include a version number or ETag with their update requests. If the version does not match the server's current version, the server returns 409, indicating that another client modified the resource in the meantime. This prevents one user's changes from silently overwriting another's, which is known as the "lost update" problem.

Beyond concurrency, 409 is also used for business rule violations that depend on the current state of the system. Examples include trying to delete a user account that has active subscriptions, creating a resource with a unique constraint violation, or transitioning a workflow to an invalid state. In all these cases, the request is syntactically valid but logically incompatible with the system's current state.

Common Causes

Duplicate Resource Creation

Attempting to create a resource that already exists. For example, trying to register a user with an email address that is already taken, or creating a record with a duplicate unique key.

Optimistic Locking Conflict

Updating a resource using an outdated version. The resource was modified by another user or process after you read it. Your version number or ETag no longer matches the current state.

State Machine Violation

Trying to transition a resource to an invalid state. For example, trying to ship an order that has already been cancelled, or approving a document that is in draft status.

Concurrent Modification

Two clients are modifying the same resource simultaneously. The server detects the conflict and rejects the second modification to prevent data corruption.

How to Fix

Re-Read and Retry

Fetch the latest version of the resource, apply your changes to the updated version, and submit again. This is the standard approach for optimistic locking conflicts.

Check for Uniqueness

Before creating a resource, check if one with the same unique identifier already exists. Handle the conflict gracefully by offering to update the existing resource instead.

Use Version Headers

Include If-Match with the resource's ETag or a version number when updating. This enables the server to detect conflicts and reject stale updates with a 409.

Handle Conflicts in UI

Show the user both versions of the data and let them merge the changes manually. This is common in collaborative editing tools and version control systems.

Code Examples

Express.js

// Express.js — handling conflicts with optimistic locking
const express = require('express');
const app = express();
app.use(express.json());

// Duplicate resource detection
app.post('/api/users', async (req, res) => {
  const { email, name } = req.body;

  const existing = await User.findOne({ where: { email } });
  if (existing) {
    return res.status(409).json({
      error: 'Conflict',
      message: `A user with email ${email} already exists`,
      existingId: existing.id
    });
  }

  const user = await User.create({ email, name });
  res.status(201).json(user);
});

// Optimistic locking with version numbers
app.put('/api/posts/:id', async (req, res) => {
  const { title, body, version } = req.body;
  const post = await Post.findById(req.params.id);

  if (!post) return res.status(404).json({ error: 'Not found' });

  if (post.version !== version) {
    return res.status(409).json({
      error: 'Conflict',
      message: 'Resource was modified by another request',
      currentVersion: post.version,
      yourVersion: version
    });
  }

  post.title = title;
  post.body = body;
  post.version += 1;
  await post.save();
  res.json(post);
});

Flask (Python)

# Flask — handling conflicts with optimistic locking
from flask import Flask, jsonify, request

app = Flask(__name__)

# Duplicate resource detection
@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    email = data.get('email')

    existing = User.query.filter_by(email=email).first()
    if existing:
        return jsonify(
            error='Conflict',
            message=f'A user with email {email} already exists',
            existing_id=existing.id
        ), 409

    user = User(email=email, name=data.get('name'))
    db.session.add(user)
    db.session.commit()
    return jsonify(user.to_dict()), 201

# Optimistic locking with version numbers
@app.route('/api/posts/<int:post_id>', methods=['PUT'])
def update_post(post_id):
    data = request.get_json()
    post = Post.query.get_or_404(post_id)

    if post.version != data.get('version'):
        return jsonify(
            error='Conflict',
            message='Resource modified by another request',
            current_version=post.version,
            your_version=data.get('version')
        ), 409

    post.title = data['title']
    post.body = data['body']
    post.version += 1
    db.session.commit()
    return jsonify(post.to_dict())

Frequently Asked Questions

What is the difference between 409 and 400?

HTTP 400 means the request is syntactically invalid and cannot be parsed. HTTP 409 means the request is syntactically valid but conflicts with the current state of the resource. A 400 error requires fixing the request format. A 409 requires resolving the logical conflict.

When should I return 409 vs 422?

Return 409 when the request conflicts with the current state of a resource, such as duplicate creation or version conflicts. Return 422 when the request is well-formed but contains semantically invalid data, such as an invalid email format or out-of-range values.

How do I implement optimistic locking?

Add a version field to your resources. When clients update a resource, they must include the version they read. The server compares versions: if they match, the update proceeds and version increments. If they differ, the server returns 409 Conflict.

Can I prevent 409 errors?

You cannot eliminate 409 errors in concurrent systems, but you can minimize them. Use fine-grained locking, reduce the time between reading and updating resources, and implement automatic retry logic with exponential backoff.

Should the 409 response include the conflicting data?

Yes, ideally. Including the current state of the resource in the 409 response allows clients to merge their changes without making an additional GET request. Include the current version number and modified fields.

Monitor Your APIs & Services

Get instant alerts when your endpoints go down. 60-second checks, free forever.

Start Monitoring Free →