DynamoDB ConditionalCheckFailedException Explained

ConditionalCheckFailedException is DynamoDB telling you a condition wasn't met — often by design. When it's expected, when it's a bug, and how to handle it correctly.

3 min read· Updated Jul 26, 2026
On this page

Unlike most DynamoDB exceptions, ConditionalCheckFailedException is frequently not a bug — it’s DynamoDB correctly telling you that a condition you attached to a write wasn’t satisfied:

ConditionalCheckFailedException: The conditional request failed

Whether that’s a problem depends entirely on why you wrote the condition. Let’s separate the expected cases from the real bugs.

When it’s expected (and you should just handle it)

Create-if-not-exists

The classic pattern: write an item only if it doesn’t already exist, using attribute_not_exists:

new PutItemCommand({
  TableName: "Users",
  Item: { pk: { S: "USER#42" }, email: { S: "a@b.com" } },
  ConditionExpression: "attribute_not_exists(pk)"
});

If a user with that key already exists, the write fails with ConditionalCheckFailedExceptionby design. That’s your “already registered” branch, not an outage. Catch it:

try {
  await client.send(putCommand);
} catch (err) {
  if (err.name === "ConditionalCheckFailedException") {
    return { alreadyExists: true };   // expected business branch
  }
  throw err;                          // anything else is a real error
}

Optimistic locking

To avoid lost updates under concurrency, guard writes with a version attribute:

new UpdateItemCommand({
  TableName: "Orders",
  Key: { pk: { S: "ORDER#1001" } },
  UpdateExpression: "SET #s = :new, version = :next",
  ConditionExpression: "version = :expected",
  ExpressionAttributeNames: { "#s": "status" },
  ExpressionAttributeValues: {
    ":new": { S: "shipped" }, ":next": { N: "8" }, ":expected": { N: "7" }
  }
});

A failure here means another writer changed the item first. The correct response is usually read the latest item and retry, not crash.

When it’s a real bug

The condition doesn’t match the data you think is there

If a write should succeed but keeps failing the check, your assumption about the item’s current state is wrong. Read the item and compare it against the condition:

  • Is the attribute you’re testing actually present? attribute_exists(x) fails if x was never set.
  • Is the value the type you expect? version = :expected with :expected as a String won’t match a Number-typed version.
  • Did a previous partial write leave the item in an unexpected shape?

attribute_exists / attribute_not_exists on the wrong attribute

attribute_not_exists(pk) tests the partition key to detect a brand-new item. Testing a non-key attribute that’s sometimes absent will make the condition pass or fail unpredictably. For “is this a new item?”, always test a key attribute.

Transactions: one failed condition fails everything

In a TransactWriteItems call, if any item’s condition fails, the entire transaction is cancelled with a TransactionCanceledException whose CancellationReasons include ConditionalCheckFailed. Inspect the reasons array to find which item failed:

TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None]

The first slot maps to the first item in the transaction. See Transactions for the full model.

Two things people miss

  • Failed conditions still cost capacity. A rejected conditional write consumes write capacity units. High-volume conditional writes on a hot key add up.
  • Get the old values back. Set ReturnValuesOnConditionCheckFailure: "ALL_OLD" on the request to receive the item’s current attributes in the exception — invaluable for debugging why the condition failed without a separate read.

Handle it, don’t fear it

The right mental model: a condition expression is a rule, and ConditionalCheckFailedException is DynamoDB enforcing it. Decide up front whether a given failure is an expected branch (catch and handle) or an invariant violation (read, reconcile, retry).

Tablyne lets you inspect an item’s exact current attributes and types, and build conditional writes visually — so when a condition fails, you can see the real state of the item immediately instead of blind-debugging. Download Tablyne free.

Frequently asked questions

Is ConditionalCheckFailedException an error I should fix?

Often it's not an error at all — it's the expected outcome of a condition you wrote. For example, a create-if-not-exists that fails because the item already exists is working as designed. Catch it and handle it as a normal branch, not a failure.

How do I prevent overwriting an existing item?

Add a ConditionExpression of attribute_not_exists(pk) to your PutItem. If an item with that key already exists, the write is rejected with ConditionalCheckFailedException instead of silently overwriting.

Does a failed condition still consume capacity?

Yes. A conditional write that fails the check still consumes write capacity units. This matters if you rely heavily on conditional writes in a hot path.

Keep reading