UpdateItem DynamoDB en Java (AWS SDK v2)

La mise à jour elle-même tient en un appel de builder. Ce qui coûte du temps aux développeurs Java, c'est tout ce qui l'entoure : un objet de réponse qui ne renvoie jamais null, une hiérarchie d'exceptions où l'échec intéressant est une sous-classe de celle que tu as probablement attrapée, et un client haut niveau qui ne sait pas exprimer cette opération du tout.

Code

import java.util.HashMap;
import java.util.Map;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ReturnValue;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;

public class UpdateItemExample {
    public static void main(String[] args) {
        try (DynamoDbClient ddb = DynamoDbClient.builder()
                .region(Region.US_EAST_1)
                .build()) {

            Map<String, AttributeValue> key = new HashMap<>();
            key.put("Artist", AttributeValue.builder().s("Arturo Sandoval").build());
            key.put("SongTitle", AttributeValue.builder().s("Cubano Chant").build());

            Map<String, String> names = new HashMap<>();
            names.put("#upd0", "Genre");
            names.put("#upd1", "Year");
            names.put("#upd2", "Awards");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":updValue0", AttributeValue.builder().s("Latin Jazz").build());
            values.put(":updValue1", AttributeValue.builder().n("1994").build());
            values.put(":updValue2", AttributeValue.builder().n("1").build());

            UpdateItemRequest request = UpdateItemRequest.builder()
                    .tableName("Music")
                    .key(key)
                    .updateExpression("SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2")
                    .expressionAttributeNames(names)
                    .expressionAttributeValues(values)
                    .returnValues(ReturnValue.ALL_NEW)
                    .build();

            UpdateItemResponse response = ddb.updateItem(request);
            System.out.println(response.attributes()); // the item after the update
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
    }
}

Explication

  • AttributeValue.builder().n("1994") prend un String, tout comme la forme plus courte AttributeValue.fromN("1994"). Il n'y a pas de surcharge n(int), parce que les nombres DynamoDB tiennent 38 chiffres significatifs et qu'aucun primitif Java n'en fait autant. À la relecture, attributes().get("Awards").n() est un String lui aussi ; l'accesseur du mauvais type renvoie null au lieu de lever, donc .s() sur un nombre est un null silencieux, et .type() te dit lequel est renseigné.

  • response.attributes() ne renvoie jamais null. Avec ReturnValue.NONE, il renvoie une DefaultSdkAutoConstructMap vide mais non nulle : un test de nullité ne se déclenche donc jamais, et un test isEmpty() ne peut pas distinguer « le service n'a rien envoyé » de « l'élément n'a aucun attribut ». Le hasAttributes() généré est l'accesseur qui connaît la différence. Chaque membre de type collection de ce SDK en possède un.

  • Le builder type-vérifie tout sauf ce qui compte. updateExpression(String) accepte n'importe quelle chaîne ; le compilateur ne distingue pas SET d'une faute de frappe, donc les erreurs d'expression sont des 400 à l'exécution. ADD #upd2 :updValue2 est l'incrément atomique, une conditionExpression en attribute_exists(Artist) rend l'appel exclusivement modificateur, et la grammaire est dans les expressions de mise à jour.

  • Préfère l'expression à la map attributeUpdates héritée. De vieux exemples la montrent encore ; elle ne sait exprimer ni plusieurs types de clauses, ni des alias, ni une condition dans une même requête.

  • getMessage() n'est pas le message du service. Le SDK y accole son propre détail de transport :

    The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: d99b117c-edd6-4dc9-8d3a-a5fa4fe9666c) (SDK Attempt Count: 1)

    Journalise ça si tu veux l'identifiant de requête pour un ticket de support. Compare plutôt sur awsErrorDetails().errorCode(), et utilise awsErrorDetails().errorMessage() quand tu veux la chaîne nue.

L'ordre des catch compte plus que d'habitude ici

ConditionalCheckFailedException extends DynamoDbException : un catch (DynamoDbException e) placé en premier avale donc le seul échec sur lequel tu voulais presque certainement brancher. Attrape d'abord le type précis, et récupère l'élément au passage :

} catch (ConditionalCheckFailedException e) {
    // with .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
    if (e.hasItem()) {
        Map<String, AttributeValue> loser = e.item();  // the item as it actually was
    }
} catch (DynamoDbException e) {
    // everything else
}

e.retryable() vaut false sur celle-ci, et c'est correct : réessayer une condition en échec échoue simplement à nouveau.

L'asymétrie à retenir, c'est que ValidationException n'a aucune classe dans ce SDK. Fouille dynamodb-2.35.9.jar et il n'y a rien à attraper. Un mot réservé, une expression malformée, une clé partielle : tout arrive sous forme de DynamoDbException ordinaire dont le awsErrorDetails().errorCode() vaut justement ValidationException. Dans un langage à typage statique, c'est un trou déroutant, et ça veut dire que les erreurs d'expression sont des comparaisons de chaînes à l'exécution.

C'est pour ça que les mots réservés de DynamoDB méritent un passage avant la livraison plutôt qu'après : la liste compte 573 entrées et inclut Year, Name et Status, dont aucun n'a l'air dangereux dans un bean Java. Pour parcourir la table brute plutôt que le bean posé dessus, télécharge DynoTable.

Le client enhanced ne sait pas exprimer ceci

Si le reste de ton accès aux données passe par DynamoDbEnhancedClient et des beans annotés, c'est l'opération qui te ramène à DynamoDbClient. Une réflexion sur UpdateItemEnhancedRequest.Builder fait apparaître item, conditionExpression, ignoreNulls, ignoreNullsMode, returnValues, returnValuesOnConditionCheckFailure, returnConsumedCapacity et returnItemCollectionMetrics. Aucune méthode n'accepte d'expression de mise à jour.

La conséquence pratique, c'est le compteur atomique. ADD #upd2 :updValue2 incrémente Awards côté serveur sans lecture préalable ; le client enhanced te donne un bean mappé et ignoreNulls pour décider si les champs absents sont supprimés, et rien qui se compile en ADD. Un cycle lecture-modification-écriture à travers un bean est une course à la mise à jour perdue en cas de concurrence, ce que l'extrait de cette page évite précisément.

Exemples liés

Références

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

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.