DynamoDB TransactionCanceledException

TL;DR — One (or more) items in your TransactWriteItems / TransactGetItems failed, so DynamoDB rolled back the entire transaction. The real cause is in the CancellationReasons array — read it; the reason Code per item tells you exactly which item and why.

What it means

DynamoDB transactions are all-or-nothing. If any item's condition fails, capacity is exceeded, or two transactions collide, the whole thing is cancelled and nothing is written. The top-level message is generic:

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

The bracketed list is positional — one entry per item in your transaction, in order. DynamoDB returns this exception with HTTP status 400, and the AWS SDKs do not retry it automatically — your code decides, per reason code, whether a retry makes sense.

Why it happens (the reason codes)

  • ConditionalCheckFailed — that item's ConditionExpression evaluated to false (see ConditionalCheckFailedException).
  • TransactionConflict — another concurrent transaction (or write) is operating on the same item; retry with backoff.
  • ProvisionedThroughputExceeded — the item's table/index ran out of capacity.
  • ThrottlingError — the table or index (typically on-demand, while DynamoDB is still scaling it) throttled the write; retry with backoff.
  • ValidationError — that item was malformed (invalid parameter values, document path, operand type, size overflow, …).
  • ItemCollectionSizeLimitExceeded — an LSI item collection hit 10 GB.
  • None — that item was fine; the failure was elsewhere in the list.

That is the complete documented code set. Note that a duplicate item key (the same item targeted by two actions) is not a cancellation code — DynamoDB rejects that request up front as a ValidationException instead.

How to fix it

  1. Read CancellationReasons off the error, not just the message. Map each entry back to your input item by index.
  2. Branch by code: ConditionalCheckFailed → business logic; TransactionConflict/ThrottlingError/ProvisionedThroughputExceeded → retry with exponential backoff; ValidationError → fix the request.
  3. Avoid duplicate keys — a single transaction can't touch the same item twice.

Editing items by hand? DynoTable's staging area batches your edits into one transactional write and lets you review every item before committing — the same all-or-nothing semantics without hand-building the request.

Example

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

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

try {
  await doc.send(new TransactWriteCommand({TransactItems: [/* ... */]}));
} catch (err) {
  if (err instanceof TransactionCanceledException) {
    for (const [i, reason] of (err.CancellationReasons ?? []).entries()) {
      if (reason.Code && reason.Code !== 'None') {
        console.error(`item ${i} cancelled: ${reason.Code}${reason.Message}`);
      }
    }
  }
  throw err;
}

FAQ

Why did my DynamoDB transaction get cancelled? One item in the TransactWriteItems/TransactGetItems failed — a condition check, a throughput/throttling limit, or a conflict with another concurrent transaction — so DynamoDB rolled the whole transaction back and wrote nothing. The per-item reason is in the CancellationReasons array.

How do I find which item in the transaction failed? Read the CancellationReasons array on the TransactionCanceledException. It has one entry per input item, in the same order; the entry whose Code is not "None" is the item that caused the cancellation.

Reproduce it

Two writes in one transaction, the second guarded by a condition that cannot hold. The whole transaction rolls back, and the per-action verdicts arrive in CancellationReasons — positionally aligned with TransactItems:

await client.send(
  new TransactWriteItemsCommand({
    TransactItems: [
      {Put: {TableName: 'orders', Item: {pk: {S: 'OK'}, sk: {S: 'META'}}}},
      {
        Put: {
          TableName: 'orders',
          Item: {pk: {S: 'ORDER#1'}, sk: {S: 'META'}},
          ConditionExpression: 'attribute_not_exists(pk)' // ORDER#1 already exists
        }
      }
    ]
  })
);

Real output:

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

error.CancellationReasons:
[
  {
    "Code": "None"
  },
  {
    "Code": "ConditionalCheckFailed",
    "Message": "The conditional request failed"
  }
]

The first action reports None — it did not fail, it was rolled back because its neighbour did. Only the entry whose Code is not None identifies the actual culprit, and its index is the index of the offending action in your own TransactItems array.

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.