DuplicateItemException: duplicate primary key

TL;DR — A PartiQL INSERT is a strict create: if an item with the same primary key already exists, DynamoDB throws DuplicateItemException instead of overwriting it. To modify the existing item use PartiQL UPDATE; to get PutItem's replace-if-exists semantics, use PutItem itself — PartiQL INSERT deliberately never replaces.

What it means

DuplicateItemException: There was an attempt to insert an item with the
same primary key as an item that already exists in the DynamoDB table.

The PartiQL data-plane (ExecuteStatement / ExecuteTransaction / BatchExecuteStatement) maps INSERT to a conditional create — it succeeds only when no item with that primary key exists. That's the opposite default from the native PutItem, which silently replaces an existing item. If you came from SQL expecting INSERT to fail on a duplicate key, this is exactly that behavior; if you expected upsert semantics, this error is the surprise.

Why it happens

  • The item genuinely already exists — a retry, a replay, or two writers racing on the same key both issuing INSERT.
  • You wanted an update, not a create — porting PutItem-style code to PartiQL and assuming INSERT replaces.
  • A non-idempotent retry loop — the first attempt succeeded but the response was lost (timeout), and the retry re-inserts the same key.
  • A synthetic key that isn't unique — the partition/sort key you compose collides more often than you think (e.g. a timestamp at second precision).

How to fix it

  1. Updating an existing item? Use UPDATE:

    UPDATE "orders" SET status = 'shipped' WHERE pk = 'ORDER#123' AND sk = 'META'
  2. Want replace-if-exists (PutItem semantics)? Call PutItem — PartiQL has no upsert statement, and the native API's default is exactly the overwrite you're after:

    await client.send(new PutItemCommand({TableName: 'orders', Item: item}));
  3. Treat it as success when the create is idempotent — if a retry hits DuplicateItemException for an item your first attempt already wrote, catching and ignoring it is often the correct handling.

  4. Keep INSERT when you rely on uniqueness — the exception is your create-only guard, the PartiQL equivalent of attribute_not_exists() on a PutItem condition.

Trying statements against real data is the quickest way to internalize the INSERT/UPDATE split — DynoTable's PartiQL editor runs statements against your live tables with inline diagnostics and quick-fixes, and the DynamoDB Expression Builder emits the equivalent native-API requests when you need PutItem semantics instead.

Reproduce it

A PartiQL INSERT for a primary key that already exists:

await client.send(
  new PutItemCommand({
    TableName: 'orders',
    Item: {pk: {S: 'ORDER#1'}, sk: {S: 'META'}}
  })
);
await client.send(
  new ExecuteStatementCommand({
    Statement: `INSERT INTO "orders" VALUE {'pk':'ORDER#1','sk':'META'}`
  })
);

Real output:

DuplicateItem: Duplicate primary key exists in table
HTTP 400

Worth knowing if you test locally: DynamoDB Local surfaces this as DuplicateItem with the short message above, while the service API reference documents it as DuplicateItemException with a longer sentence. Match on the HTTP 400 plus the operation, not on the exact name or wording, or your handler will behave differently against Local than against the real table.

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.