DynamoDB ConditionalCheckFailedException

En bref — Ton écriture portait un ConditionExpression qui s'est évalué à false face à l'élément actuel, donc DynamoDB a rejeté l'écriture et a laissé l'élément intact. C'est généralement attendu (concurrence optimiste, « créer s'il n'existe pas ») — attrape-la et branche, ne réessaie pas aveuglément.

Ce que ça signifie

Contrairement à une ValidationException, la requête était bien formée. DynamoDB a évalué ta condition et elle n'a pas tenu, donc le PutItem / UpdateItem / DeleteItem (ou un élément unique dans un TransactWriteItems) a été refusé. Aucune donnée n'a changé. Elle renvoie HTTP 400 et n'est pas réessayable telle quelle.

Pourquoi ça arrive

  • Garde attribute_not_exists(pk) sur une création — l'élément existe déjà (un insert dupliqué).
  • Garde attribute_exists(pk) sur une mise à jour/suppression — l'élément a disparu.
  • Concurrence optimiste — une vérification version = :expected (ou updatedAt) où un autre écrivain est arrivé le premier.
  • Gardes de règle métierbalance >= :amount, #status = :expected qui ne correspondent plus à l'élément stocké.

Comment le corriger

  1. Traite-la comme un résultat normal, pas une faute. Attrape l'exception et décide ce qu'une condition en échec signifie dans ton flux (l'élément existe déjà → renvoie-le ; version périmée → relis et réessaie avec la nouvelle version).
  2. Relis l'élément actuel. Définis ReturnValuesOnConditionCheckFailure: 'ALL_OLD' pour obtenir l'élément qui a causé l'échec sans second aller-retour — il revient sur l'exception elle-même (le champ Item), et aucune capacité de lecture n'est consommée.
  3. Relis + recalcule pour la concurrence, puis retente avec la version fraîche — ne renvoie pas simplement la même valeur attendue.

Cette boucle relire-et-comparer est exactement ce que fait la zone de staging de DynoTable pour les modifications manuelles — elle met tes écritures en staging et, en cas de conflit de verrouillage optimiste, t'affiche l'élément actuel à côté de ta modification pour que tu résolves le conflit avant que quoi que ce soit ne soit envoyé.

Exemple

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

Qu'est-ce qui provoque une ConditionalCheckFailedException dans DynamoDB ? Une écriture (PutItem, UpdateItem, DeleteItem ou un élément TransactWrite) portait un ConditionExpression qui s'est évalué à faux face à l'élément actuel — par exemple attribute_not_exists(pk) sur une clé qui existe déjà, ou une vérification de version qui ne correspond plus. DynamoDB rejette l'écriture et laisse l'élément inchangé.

Comment empêcher une ConditionalCheckFailedException de faire planter mon application ? Attrape l'exception et traite-la comme un résultat attendu, pas une faute. Une condition en échec signifie généralement « quelqu'un d'autre est arrivé le premier » (concurrence optimiste) ou « l'élément existe déjà » — branche dessus plutôt que de réessayer aveuglément.

Reproduire l'erreur

Un PutItem gardé par attribute_not_exists sur une clé qui, elle, existe :

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

Sortie réelle :

ConditionalCheckFailedException: The conditional request failed
HTTP 400

Le message est délibérément avare : il ne dit jamais quelle partie de la condition a échoué, ni ce que l'élément contenait réellement. Passe ReturnValuesOnConditionCheckFailure: "ALL_OLD" et l'élément actuel te revient sur error.Item, ce qui transforme la devinette en diff.

Erreurs liées

Références

Dernière vérification le 2026-07-13 par rapport à la documentation officielle AWS liée ci-dessus.

Reproduit le 2026-07-26 sur DynamoDB Local 2.x avec l'AWS SDK for JavaScript v3.1095.0 — la sortie ci-dessus est reproduite telle quelle.

Travaille avec DynamoDB sans la Console

Un client de bureau rapide pour DynamoDB qui exécute le vrai SQL que DynamoDB ne peut pas — JOINs, GROUP BY, agrégations — avec édition visuelle et un agent IA sur tes propres clés Bedrock.

Essai gratuit de 30 jours, sans carte bancaire — ensuite la formule Gratuit, sans limite de durée.