DynamoDB PutItem en Java (AWS SDK v2)

PutItem écrit un élément entier et remplace tout élément existant portant la même clé primaire (les actions par élément expliquent en quoi cela diffère d'UpdateItem). Dans l'AWS SDK for Java 2.x, chaque attribut entre dans un PutItemRequest sous forme d'AttributeValue typé, et le builder te laissera en construire un qui ne peut pas être valide.

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.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;

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

            Map<String, AttributeValue> item = new HashMap<>();
            item.put("Artist", AttributeValue.builder().s("Arturo Sandoval").build());
            item.put("SongTitle", AttributeValue.builder().s("Cubano Chant").build());
            item.put("AlbumTitle", AttributeValue.builder().s("Danzon").build());
            item.put("Year", AttributeValue.builder().n("1994").build());
            item.put("Awards", AttributeValue.builder().n("0").build());

            Map<String, String> names = new HashMap<>();
            names.put("#cond0", "Artist");
            names.put("#cond1", "SongTitle");

            PutItemRequest request = PutItemRequest.builder()
                    .tableName("Music")
                    .item(item)
                    .conditionExpression("attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)")
                    .expressionAttributeNames(names)
                    .build();

            try {
                ddb.putItem(request);
                System.out.println("Song written");
            } catch (ConditionalCheckFailedException e) {
                System.out.println("A song with that key already exists — not overwritten");
            }
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
    }
}

Explication

AttributeValue.builder().build() compile. Et il est aussi inenvoyable. Le builder n'a aucun champ obligatoire : un attribut où tu as oublié le .s(...) passe parfaitement le typage et échoue au niveau du service :

DynamoDbException | ValidationException | Supplied AttributeValue is empty, must contain exactly one of the supported datatypes

C'est la forme spécifiquement Java d'une erreur que d'autres SDK rendent impossible : les types.AttributeValueMember* de Go sont des types distincts, il n'y a donc rien à laisser non renseigné. Voir "Supplied AttributeValue is empty" pour le correctif général.

Un null Java passé à .s(...) ne devient pas un NULL DynamoDB. C'est la version qui mord vraiment, parce qu'elle a l'air d'une valeur :

AttributeValue.builder().s(customer.getNotes()).build()   // getNotes() returned null

Aucune NullPointerException n'est levée à la construction. Le builder n'enregistre simplement rien, et la requête échoue avec le message Supplied AttributeValue is empty identique, en pointant un attribut que tu n'avais pas soupçonné. Si tu veux un vrai null, c'est AttributeValue.builder().nul(true).build() ; le plus souvent, ce que tu veux, c'est omettre l'entrée. Note que c'est l'inverse du SDK Go, où un pointeur nil est marshallé en NULL et crée silencieusement un attribut ; les deux ont été exécutés sur le même moteur le même jour.

getMessage() n'est pas le message du service. Le SDK y ajoute son propre contexte, si bien que la chaîne est :

The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 77a08ef1-a3a9-4f97-b309-4cb0741edd1a) (SDK Attempt Count: 1)

Fais la correspondance sur e.awsErrorDetails().errorCode() et lis e.awsErrorDetails().errorMessage() pour le texte nu. Tout ce qui compare getMessage() à un littéral est cassé par une reprise, qui change le nombre de tentatives.

Attrape ConditionalCheckFailedException avant DynamoDbException, et lis ce qu'elle transporte. Elle étend DynamoDbException : ordonner les blocs catch dans l'autre sens rend le handler spécifique inatteignable. Sur l'objet attrapé, statusCode() a renvoyé 400 et retryable() a renvoyé false, ce qui est la réponse honnête pour un rejet de logique métier. Ajoute .returnValuesOnConditionCheckFailure("ALL_OLD") à la requête et e.item() revient rempli avec l'élément qui a bloqué l'écriture (cinq attributs dans l'exécution ci-dessus, Year en AttributeValue(N=1994)) : tu n'as donc pas besoin d'un getItem de suivi pour savoir qui a gagné.

Les nombres entrent sous forme de chaînes via .n(...). Le type N de DynamoDB est du texte décimal sur le réseau, et c'est ce qui empêche 1994 de devenir un double. .n(String.valueOf(year)) est l'idiome ; il n'y a pas de surcharge .n(int) à laquelle se raccrocher.

Le client est Closeable, et fait pour durer. Le try-with-resources ci-dessus convient à un programme one-shot et pas à un service : DynamoDbClient possède un pool de connexions HTTP et est thread-safe, donc construis-en un par application et laisse-le vivre. En construire un par requête est le bug de performance Java le plus courant sur cette API.

Tu préfères des beans aux maps d'AttributeValue ? Le DynamoDB Enhanced Client (software.amazon.awssdk.enhanced.dynamodb) mappe une classe annotée directement vers un élément, ce qui élimine complètement le piège du builder vide. Ça coûte un scan réflexif TableSchema.fromBean au démarrage, que StaticTableSchema évite si ça compte pour toi.

Le faire visuellement

Tous les échecs ci-dessus commencent par des valeurs typées construites à la main. Le convertisseur JSON DynamoDB gratuit prend du JSON ordinaire et renvoie la forme typée, pour que tu voies exactement à quoi l'élément doit ressembler sur le réseau avant d'écrire le moindre AttributeValue.builder().

Pour écrire et modifier des éléments dans tes propres tables — un formulaire par attribut, des sélecteurs de type, le résultat recopié en Java — télécharge DynoTable.

Exemples liés

Références

Reproduit le 2026-07-28 avec l'AWS SDK for Java 2.49.4 sur OpenJDK 26.0.1, sur DynamoDB Local (amazon/dynamodb-local) sur le port 9000. Le texte de l'exception, le code de statut et le contenu de l'élément ci-dessus sont la sortie capturée, copiée 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.