How to fix ConditionalCheckFailedException: The conditional request failed in DynamoDB
This error occurs when a DynamoDB conditional write operation fails because the specified condition expression evaluated to false. It commonly happens during optimistic locking when the item has been modified by another process, or when custom conditional checks fail.
Quick fix
Fixes ConditionalCheckFailedException
// AWS SDK v3 (JavaScript/TypeScript)
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({});
const command = new UpdateItemCommand({
TableName: "Users",
Key: { userId: { S: "user123" } },
UpdateExpression: "SET #status = :newStatus",
ConditionExpression: "#version = :expectedVersion",
ExpressionAttributeNames: {
"#status": "status",
"#version": "version"
},
ExpressionAttributeValues: {
":newStatus": { S: "active" },
":expectedVersion": { N: "5" }
},
ReturnValuesOnConditionCheckFailure: "ALL_OLD" // Returns item state on failure
});
try {
await client.send(command);
} catch (error) {
if (error.name === "ConditionalCheckFailedException") {
console.log("Current item state:", error.Item);
// Item will show actual version number and other attributes
}
}- Enable ReturnValuesOnConditionCheckFailure to diagnose the issue
- Implement proper retry logic with exponential backoff
- Verify your ConditionExpression syntax and attribute names
- Use attribute_exists() or attribute_not_exists() for safer conditions
// AWS SDK v3 (JavaScript/TypeScript)import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";const client = new DynamoDBClient({});const command = new UpdateItemCommand({TableName: "Users",Key: { userId: { S: "user123" } },UpdateExpression: "SET #status = :newStatus",ConditionExpression: "#version = :expectedVersion",ExpressionAttributeNames: {"#status": "status","#version": "version"},ExpressionAttributeValues: {":newStatus": { S: "active" },":expectedVersion": { N: "5" }},ReturnValuesOnConditionCheckFailure: "ALL_OLD" // Returns item state on failure});try {await client.send(command);} catch (error) {if (error.name === "ConditionalCheckFailedException") {console.log("Current item state:", error.Item);// Item will show actual version number and other attributes}}ConditionalCheckFailedException: The conditional request failed
What this error means
The ConditionalCheckFailedException is thrown by AWS SDK or CLI when a ConditionExpression you specified evaluates to false during a write operation (PutItem, UpdateItem, or DeleteItem). DynamoDB processes the condition before performing the write, and if the condition isn't met, it rejects the entire operation with this exception. This is an HTTP 400 client-side error, meaning the condition logic or expected values in your application code need to be corrected. The error protects data integrity by ensuring that writes only occur when specific conditions are satisfied, such as when an item hasn't been modified by another client (optimistic locking) or when specific attribute values match expected states. Unlike server-side errors, this exception indicates that your conditional logic is working as designed—it's preventing an operation that shouldn't proceed based on the current state of the data.
Typical symptoms
- Write operations (PutItem, UpdateItem, DeleteItem) fail with HTTP 400 status code
- Application logs show "ConditionalCheckFailedException" or "The conditional request failed"
- Updates fail in high-concurrency scenarios where multiple clients modify the same item
- Optimistic locking failures when version numbers don't match
- Conditional writes succeed when tested individually but fail under load
Common causes
- The ConditionExpression evaluated to false because the item's current state doesn't match expected values
- Optimistic locking version mismatch—another client modified the item between your read and write operations
- Incorrect attribute names or values in the ConditionExpression (typos, wrong data types)
- Item doesn't exist when using conditions like attribute_exists() or checking specific attribute values
- Race conditions in concurrent write scenarios where multiple processes compete to update the same item
- Using the wrong comparison operator or logical expression in complex conditions
How to fix "ConditionalCheckFailedException: The conditional request failed"
Set the ReturnValuesOnConditionCheckFailure parameter to ALL_OLD to see the actual item state that caused the condition to fail:
// AWS SDK v3 (JavaScript/TypeScript)
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({});
const command = new UpdateItemCommand({
TableName: "Users",
Key: { userId: { S: "user123" } },
UpdateExpression: "SET #status = :newStatus",
ConditionExpression: "#version = :expectedVersion",
ExpressionAttributeNames: {
"#status": "status",
"#version": "version"
},
ExpressionAttributeValues: {
":newStatus": { S: "active" },
":expectedVersion": { N: "5" }
},
ReturnValuesOnConditionCheckFailure: "ALL_OLD" // Returns item state on failure
});
try {
await client.send(command);
} catch (error) {
if (error.name === "ConditionalCheckFailedException") {
console.log("Current item state:", error.Item);
// Item will show actual version number and other attributes
}
}# AWS SDK (Python/boto3)
import boto3
from botocore.exceptions import ClientError
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Users')
try:
table.update_item(
Key={'userId': 'user123'},
UpdateExpression='SET #status = :newStatus',
ConditionExpression='#version = :expectedVersion',
ExpressionAttributeNames={
'#status': 'status',
'#version': 'version'
},
ExpressionAttributeValues={
':newStatus': 'active',
':expectedVersion': 5
},
ReturnValuesOnConditionCheckFailure='ALL_OLD'
)
except ClientError as e:
if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
print("Current item:", e.response.get('Item'))This reveals whether the version number is wrong, an attribute is missing, or values don't match expectations.
For optimistic locking scenarios, implement a retry mechanism that reads the latest item and reattempts the update:
async function updateWithRetry(userId, newStatus, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
// Read current item to get latest version
const getCommand = new GetItemCommand({
TableName: "Users",
Key: { userId: { S: userId } }
});
const { Item } = await client.send(getCommand);
const currentVersion = parseInt(Item.version.N);
// Attempt conditional update
const updateCommand = new UpdateItemCommand({
TableName: "Users",
Key: { userId: { S: userId } },
UpdateExpression: "SET #status = :newStatus, #version = :newVersion",
ConditionExpression: "#version = :expectedVersion",
ExpressionAttributeNames: {
"#status": "status",
"#version": "version"
},
ExpressionAttributeValues: {
":newStatus": { S: newStatus },
":expectedVersion": { N: currentVersion.toString() },
":newVersion": { N: (currentVersion + 1).toString() }
}
});
await client.send(updateCommand);
console.log(`Update succeeded on attempt ${attempt + 1}`);
return; // Success
} catch (error) {
if (error.name === "ConditionalCheckFailedException") {
if (attempt < maxRetries - 1) {
const backoffMs = Math.pow(2, attempt) * 100; // 100ms, 200ms, 400ms
console.log(`Retry ${attempt + 1} after ${backoffMs}ms`);
await new Promise(resolve => setTimeout(resolve, backoffMs));
} else {
throw new Error("Max retries exceeded");
}
} else {
throw error; // Different error type
}
}
}
}This pattern handles concurrent updates gracefully by refreshing the item state before each retry.
Double-check that your condition uses correct attribute names, reserved word substitutions, and comparison operators:
// WRONG - "status" is a reserved word, missing attribute name placeholder
const badCommand = new UpdateItemCommand({
ConditionExpression: "status = :val", // Will fail or behave unexpectedly
ExpressionAttributeValues: { ":val": { S: "active" } }
});
// CORRECT - Use ExpressionAttributeNames for reserved words
const goodCommand = new UpdateItemCommand({
ConditionExpression: "#status = :val",
ExpressionAttributeNames: { "#status": "status" },
ExpressionAttributeValues: { ":val": { S: "active" } }
});
// Check for attribute existence before updating
const safeUpdate = new UpdateItemCommand({
ConditionExpression: "attribute_exists(userId) AND #version = :expectedVersion",
ExpressionAttributeNames: { "#version": "version" },
ExpressionAttributeValues: { ":expectedVersion": { N: "3" } }
});
// Verify data types match (N for numbers, S for strings, etc.)
const typeCheck = new UpdateItemCommand({
ConditionExpression: "#count > :threshold",
ExpressionAttributeNames: { "#count": "loginCount" },
ExpressionAttributeValues: {
":threshold": { N: "10" } // Use N for number, not S
}
});Common mistakes include using reserved words without placeholders, wrong data types, or typos in attribute names.
When working with conditional writes, explicitly check for attribute existence to avoid unexpected failures:
# Ensure item exists before updating
table.update_item(
Key={'userId': 'user123'},
UpdateExpression='SET lastLogin = :timestamp',
ConditionExpression='attribute_exists(userId)', # Only update if item exists
ExpressionAttributeValues={':timestamp': '2025-01-15T10:30:00Z'}
)
# Prevent duplicate creation (put only if item doesn't exist)
table.put_item(
Item={
'userId': 'user456',
'email': 'user@example.com',
'status': 'active',
'version': 1
},
ConditionExpression='attribute_not_exists(userId)' # Fail if already exists
)
# Combine existence check with value comparison
table.update_item(
Key={'orderId': 'order789'},
UpdateExpression='SET #status = :completed',
ConditionExpression='attribute_exists(orderId) AND #status = :pending',
ExpressionAttributeNames={
'#status': 'status'
},
ExpressionAttributeValues={
':pending': 'pending',
':completed': 'completed'
}
)These checks prevent operations on non-existent items or ensure items exist before complex updates.
Design your operations to be safe when retried, using conditional expressions to ensure idempotency:
// Idempotent increment - only add if not already processed
async function recordPayment(orderId, amount, transactionId) {
try {
await client.send(new UpdateItemCommand({
TableName: "Orders",
Key: { orderId: { S: orderId } },
UpdateExpression: "SET totalPaid = totalPaid + :amount, processedTransactions = list_append(if_not_exists(processedTransactions, :emptyList), :txnId)",
ConditionExpression: "NOT contains(processedTransactions, :txnId)",
ExpressionAttributeValues: {
":amount": { N: amount.toString() },
":txnId": { L: [{ S: transactionId }] },
":emptyList": { L: [] }
}
}));
return { success: true };
} catch (error) {
if (error.name === "ConditionalCheckFailedException") {
// Transaction already processed - this is OK for idempotency
console.log(`Transaction ${transactionId} already processed`);
return { success: true, alreadyProcessed: true };
}
throw error;
}
}
// State machine pattern - only allow valid state transitions
async function updateOrderStatus(orderId, newStatus, currentStatus) {
const command = new UpdateItemCommand({
TableName: "Orders",
Key: { orderId: { S: orderId } },
UpdateExpression: "SET #status = :newStatus, updatedAt = :timestamp",
ConditionExpression: "#status = :currentStatus",
ExpressionAttributeNames: { "#status": "status" },
ExpressionAttributeValues: {
":newStatus": { S: newStatus },
":currentStatus": { S: currentStatus },
":timestamp": { S: new Date().toISOString() }
}
});
await client.send(command);
}This approach treats ConditionalCheckFailedException as a valid outcome in certain scenarios.
Advanced notes
High-Concurrency Patterns:
In systems with heavy concurrent writes, consider using DynamoDB Streams to trigger downstream processes instead of blocking on conditional writes. You can also implement distributed locking with DynamoDB's conditional writes combined with TTL attributes to create temporary locks.
For scenarios requiring strong consistency guarantees across multiple items, use DynamoDB Transactions (TransactWriteItems), which provide atomic all-or-nothing operations across up to 100 items. Transactions also support conditional checks but throw TransactionCanceledException instead.
Performance Considerations:
ConditionalCheckFailedException consumes read capacity even though the write fails. Each failed conditional write uses one write capacity unit for the attempt plus read capacity for the condition evaluation. In high-failure scenarios, this can significantly impact throughput and costs.
DynamoDBMapper and ORM Support:
When using AWS SDK's DynamoDBMapper (Java), Object Persistence Model (.NET), or third-party ORMs, optimistic locking is often handled automatically via version attributes. The library increments version numbers and adds conditional checks transparently. If you encounter ConditionalCheckFailedException in this context, it means the item was modified between your read and write operations—refresh the object and retry.
Testing Conditions:
Use the ReturnValuesOnConditionCheckFailure parameter extensively during development to understand exactly why conditions fail. In production, log the returned item state to monitoring systems to identify patterns in failed conditions, such as frequently conflicting version numbers indicating hotspot contention on specific items.
Frequently asked questions
- What does "ConditionalCheckFailedException" mean?
- The ConditionalCheckFailedException is thrown by AWS SDK or CLI when a ConditionExpression you specified evaluates to false during a write operation (PutItem, UpdateItem, or DeleteItem). DynamoDB processes the condition before performing the write, and if the condition isn't met, it rejects the entire operation with this exception. This is an HTTP 400 client-side error, meaning the condition logic or expected values in your application code need to be corrected.
- How do I fix "ConditionalCheckFailedException"?
- This error occurs when a DynamoDB conditional write operation fails because the specified condition expression evaluated to false. It commonly happens during optimistic locking when the item has been modified by another process, or when custom conditional checks fail.
- What causes "ConditionalCheckFailedException"?
- The ConditionExpression evaluated to false because the item's current state doesn't match expected values Optimistic locking version mismatch—another client modified the item between your read and write operations Incorrect attribute names or values in the ConditionExpression (typos, wrong data types)
- Is the "ConditionalCheckFailedException" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.