DynamoDB PutItem in Java (AWS SDK v2)

PutItem schreibt ein ganzes Item und ersetzt jedes vorhandene Item mit demselben Primary Key (Item-basierte Aktionen behandelt, worin sich das von UpdateItem unterscheidet). Im AWS SDK for Java 2.x geht jedes Attribut als typisierter AttributeValue in ein PutItemRequest — und der Builder lässt dich einen konstruieren, der unmöglich gültig sein kann.

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());
        }
    }
}

Erklärung

AttributeValue.builder().build() kompiliert. Es ist auch nicht versendbar. Der Builder hat kein Pflichtfeld, ein Attribut, bei dem du das .s(...) vergessen hast, ist also typkorrekt und scheitert erst beim Dienst:

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

Das ist die Java-spezifische Form eines Fehlers, den andere SDKs unmöglich machen: Gos types.AttributeValueMember* sind eigene Typen, es gibt also nichts, was ungesetzt bleiben könnte. Die weitergehende Lösung steht unter "Supplied AttributeValue is empty".

Ein an .s(...) übergebenes Java-null wird kein DynamoDB-NULL. Das ist die Variante, die wirklich beißt, weil sie wie ein Wert aussieht:

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

Bei der Konstruktion wird keine NullPointerException geworfen. Der Builder merkt sich schlicht nichts, und die Anfrage scheitert mit derselben Supplied AttributeValue is empty-Meldung, die auf ein Attribut zeigt, das du nie verdächtigt hättest. Willst du ein echtes Null, ist das AttributeValue.builder().nul(true).build(); häufiger willst du den Eintrag ganz weglassen. Beachte, dass es im Go-SDK genau umgekehrt ist: Dort marshallt ein nil-Pointer zu NULL und legt stillschweigend ein Attribut an. Beides wurde am selben Tag gegen dieselbe Engine ausgeführt.

getMessage() ist nicht die Meldung des Dienstes. Das SDK hängt seinen eigenen Kontext an, der String lautet also:

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

Matche auf e.awsErrorDetails().errorCode() und lies e.awsErrorDetails().errorMessage() für den nackten Text. Alles, was getMessage() mit einem Literal vergleicht, zerbricht an einem Retry, denn der ändert den Attempt Count.

Fange ConditionalCheckFailedException vor DynamoDbException und lies aus, was sie mitbringt. Sie erweitert DynamoDbException — die catch-Blöcke andersherum zu ordnen macht den spezifischen Handler unerreichbar. Auf dem gefangenen Objekt lieferte statusCode() den Wert 400 und retryable() den Wert false, was für eine Ablehnung durch die Geschäftslogik die ehrliche Antwort ist. Ergänze .returnValuesOnConditionCheckFailure("ALL_OLD") an der Anfrage, und e.item() kommt gefüllt mit dem Item zurück, das den Write blockiert hat (fünf Attribute im obigen Lauf, Year als AttributeValue(N=1994)) — du brauchst also kein nachgelagertes getItem, um herauszufinden, wer gewonnen hat.

Zahlen gehen über .n(...) als Strings hinein. DynamoDBs N-Typ ist auf der Leitung dezimaler Text, und genau das verhindert, dass aus 1994 ein Double wird. .n(String.valueOf(year)) ist das Idiom; eine .n(int)-Überladung, nach der man greifen könnte, gibt es nicht.

Der Client ist Closeable — und langlebig. Das try-with-resources oben ist für ein Einmalprogramm richtig und für einen Dienst falsch: DynamoDbClient besitzt einen HTTP-Connection-Pool und ist thread-safe, baue also einen pro Anwendung und lass ihn leben. Einen pro Anfrage zu bauen ist der häufigste Java-Performance-Bug auf dieser API.

Lieber Beans als AttributeValue-Maps? Der DynamoDB Enhanced Client (software.amazon.awssdk.enhanced.dynamodb) bildet eine annotierte Klasse direkt auf ein Item ab, was die Falle des leeren Builders vollständig beseitigt. Er kostet beim Start einen reflexiven TableSchema.fromBean-Scan, den StaticTableSchema vermeidet, falls dir das wichtig ist.

Mach es visuell

Jeder Fehler oben beginnt mit von Hand gebauten typisierten Werten. Der kostenlose DynamoDB-JSON-Konverter nimmt gewöhnliches JSON und gibt die typisierte Form zurück, sodass du genau siehst, wie das Item auf der Leitung aussehen sollte, bevor du einen einzigen AttributeValue.builder() schreibst.

Um Items gegen deine eigenen Tabellen zu schreiben und zu bearbeiten — ein Formular pro Attribut, Typ-Auswahl, das Ergebnis als Java zurückkopieren — lade DynoTable herunter.

Verwandte Beispiele

Referenzen

Am 2026-07-28 mit AWS SDK for Java 2.49.4 auf OpenJDK 26.0.1 gegen DynamoDB Local (amazon/dynamodb-local) auf Port 9000 reproduziert. Der Exception-Text, der Statuscode und die Item-Inhalte oben sind aufgezeichnete Ausgabe, wortgetreu kopiert.

Mit DynamoDB ohne die Console arbeiten

Ein schneller DynamoDB-Desktop-Client, der das echte SQL ausführt, das DynamoDB nicht kann — JOINs, GROUP BY, Aggregationen — mit visueller Bearbeitung und einem KI-Agenten auf deinen eigenen Bedrock-Schlüsseln.

30 Tage kostenlos testen, keine Kreditkarte — danach der Kostenlos-Tarif ohne Zeitlimit.