DynamoDB Conditional Write with the AWS CLI
A conditional write is straightforward to send from the shell and awkward to read, because the interesting result of a failed one arrives as an error rather than as output. DynamoDB condition expressions covers what the expression can say; this page is about running one from the CLI and getting the losing item back out of the failure.
Code
aws dynamodb update-item \
--table-name 'Music' \
--key '{"Artist":{"S":"Arturo Sandoval"},"SongTitle":{"S":"Cubano Chant"}}' \
--update-expression 'SET #upd0 = :updValue0, #version = :newVersion' \
--condition-expression 'attribute_exists(#cond0) AND #version = :expectedVersion' \
--expression-attribute-names '{"#upd0":"Genre","#version":"Version","#cond0":"Artist"}' \
--expression-attribute-values '{":updValue0":{"S":"Latin Jazz"},":expectedVersion":{"N":"7"},":newVersion":{"N":"8"}}'On success the command prints nothing and exits 0. If another writer got there first, the condition fails and the CLI reports the service message:
An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation:
The conditional request failedExplanation
- Success — silent. No output, exit 0. There is nothing to parse and nothing to assert on, so a shell script has to treat the exit status as the result. Add
--return-values ALL_NEWif you want the updated item printed. - Failure is exit status 254, which is the CLI v2 code for a client-side error and is shared with a malformed request. Branch on the message before you retry, or a typo in your expression becomes an infinite backoff loop.
--return-values-on-condition-check-failure ALL_OLDdoes work here. Valid values areALL_OLDandNONE, and it consumes no read capacity. Getting the item out of the error takes one more flag, covered below.- Shared placeholder namespace — the condition and the update are separate flags, yet
--expression-attribute-namesand--expression-attribute-valuesare merged across--update-expressionand--condition-expression, which is why the generated names run#upd0,#cond0rather than restarting per clause. Reuse a placeholder for two different meanings and the second silently wins. - A failed write is still billed. The Developer Guide is explicit: a condition evaluating to false consumes write capacity anyway, sized on the larger of the old and new item. Conditions are not a cheap existence probe.
The failure output, and how to get the item out of it
Run the fence once and it succeeds silently. Run it a second time, when Version is no longer 7, and aws-cli/2.36.9 prints to stderr:
aws: [ERROR]: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: The conditional request failedAdd --return-values-on-condition-check-failure ALL_OLD and the default output tells you there is more, without showing it:
aws: [ERROR]: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: The conditional request failed
Additional error details:
Item: <complex value>
Use "--cli-error-format json" or another error format to see the full details.<complex value> is the item, withheld by the default text renderer. Add --cli-error-format json and the whole thing prints:
{
"Message": "The conditional request failed",
"Code": "ConditionalCheckFailedException",
"Item": {
"Artist": {"S": "Arturo Sandoval"},
"Year": {"N": "1994"},
"Version": {"N": "8"},
"SongTitle": {"S": "Cubano Chant"},
"AlbumTitle": {"S": "Danzon"},
"Genre": {"S": "Latin Jazz"}
}
}(Attribute maps folded onto one line each; everything else is as printed.) Version is 8 and Genre is set because the first run succeeded. That is the optimistic-locking loop closed from a shell script: pipe stderr through jq -r '.Item.Version.N', feed it back as :expectedVersion, retry. No get-item, and no window between the read and the retry for a third writer to slip into.
The retries are not free. Each rejected attempt consumes a write unit, so a contended key under a tight loop bills steadily while making no progress. The pricing calculator turns a write rate into a monthly figure if you want to know what a retry storm actually costs before you cap the attempts.
To run these guards against your own tables without shell-quoting the placeholder maps, download DynoTable.
Related examples
- DynamoDB conditional write in Node.js — the same optimistic lock with AWS SDK v3.
- DynamoDB conditional write in Python — the same optimistic lock with boto3.
- DynamoDB PutItem with the AWS CLI — the create-only
attribute_not_existsput. - DynamoDB condition expressions — every function, with patterns.
- Understanding ReturnValues — what each return option gives you.
- DynamoDB ConditionalCheckFailedException — when the failed check is expected, and how to handle it cheaply.
References
- UpdateItem — Amazon DynamoDB API Reference
- update-item — AWS CLI Command Reference
- Condition expressions — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
Last verified 2026-07-28 against the official AWS documentation linked above.