DynamoDB ConditionalCheckFailedException

TL;DR — Your write carried a ConditionExpression that evaluated to false against the current item, so DynamoDB rejected the write and left the item untouched. This is usually expected (optimistic concurrency, "create if not exists") — catch it and branch, don't retry blindly.

What it means

Unlike a ValidationException, the request was well-formed. DynamoDB evaluated your condition and it didn't hold, so the PutItem / UpdateItem / DeleteItem (or a single item inside a TransactWriteItems) was refused. No data changed. It returns HTTP 400 and is not retryable as-is.

Why it happens

  • attribute_not_exists(pk) guard on a create — the item already exists (a duplicate insert).
  • attribute_exists(pk) guard on an update/delete — the item is gone.
  • Optimistic concurrency — a version = :expected (or updatedAt) check where another writer got there first.
  • Business-rule guardsbalance >= :amount, #status = :expected that no longer match the stored item.

How to fix it

  1. Treat it as a normal outcome, not a fault. Catch the exception and decide what a failed condition means in your flow (item already exists → return it; version stale → re-read and retry with the new version).
  2. Read the current item back. Set ReturnValuesOnConditionCheckFailure: 'ALL_OLD' to get the item that caused the failure without a second round-trip — it comes back on the exception itself (the Item field), and no read capacity is consumed.
  3. Re-read + recompute for concurrency, then re-attempt with the fresh version — don't just resend the same expected value.

That re-read-and-compare loop is exactly what DynoTable's staging area does for hand edits — it stages your writes and, on an optimistic-locking conflict, shows you the current item next to your change so you can resolve it before anything is sent.

Example

import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient, PutCommand} from '@aws-sdk/lib-dynamodb';
import {ConditionalCheckFailedException} from '@aws-sdk/client-dynamodb';

const doc = DynamoDBDocumentClient.from(new DynamoDBClient({}));

try {
  await doc.send(
    new PutCommand({
      TableName: 'Users',
      Item: {pk: 'USER#1', email: 'a@b.com'},
      ConditionExpression: 'attribute_not_exists(pk)' // create-only
    })
  );
} catch (err) {
  if (err instanceof ConditionalCheckFailedException) {
    // Expected: the user already exists. Handle gracefully.
    return {alreadyExists: true};
  }
  throw err;
}

FAQ

What causes a ConditionalCheckFailedException in DynamoDB? A write (PutItem, UpdateItem, DeleteItem or a TransactWrite item) carried a ConditionExpression that evaluated to false against the current item — for example attribute_not_exists(pk) on a key that already exists, or a version check that no longer matches. DynamoDB rejects the write and leaves the item unchanged.

How do I stop a ConditionalCheckFailedException from crashing my app? Catch the exception and treat it as an expected outcome, not a fault. A failed condition usually means "someone else got there first" (optimistic concurrency) or "the item already exists" — branch on it instead of retrying blindly.

Reproduce it

A PutItem guarded by attribute_not_exists against a key that does exist:

await client.send(
  new PutItemCommand({
    TableName: 'orders',
    Item: {pk: {S: 'ORDER#1'}, sk: {S: 'META'}},
    ConditionExpression: 'attribute_not_exists(pk)'
  })
);

Real output:

ConditionalCheckFailedException: The conditional request failed
HTTP 400

The message is deliberately uninformative — it never says which part of the condition failed, or what the item actually held. Pass ReturnValuesOnConditionCheckFailure: "ALL_OLD" and the current item comes back on error.Item, which turns this from a guess into a diff.

References

Last verified 2026-07-13 against the official AWS documentation linked above.

Reproduced 2026-07-26 against DynamoDB Local 2.x with AWS SDK for JavaScript v3.1095.0 — the output above is verbatim.

Work with DynamoDB without the Console

A fast DynamoDB desktop client that runs the real SQL DynamoDB can’t — JOINs, GROUP BY, aggregates — with visual editing and an AI agent on your own Bedrock keys.

Free 30-day trial, no credit card — then the Free plan with no time limit.