DynamoDB UpdateItem in Java (AWS SDK v2)

Das Update selbst ist ein Builder-Aufruf. Was Java-Entwickler Zeit kostet, ist alles drumherum: ein Antwortobjekt, das nie null zurückgibt, eine Exception-Hierarchie, in der der interessante Fehlschlag eine Unterklasse dessen ist, was du vermutlich abgefangen hast, und ein High-Level-Client, der diese Operation überhaupt nicht ausdrücken 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.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());
        }
    }
}

Erklärung

  • AttributeValue.builder().n("1994") nimmt einen String, und das kürzere AttributeValue.fromN("1994") ebenso. Es gibt keine n(int)-Überladung, denn DynamoDB-Zahlen halten 38 signifikante Stellen und kein Java-Primitive tut das. Beim Zurücklesen ist attributes().get("Awards").n() ebenfalls ein String; der Accessor für den falschen Typ gibt null zurück, statt zu werfen — .s() auf einer Zahl ist also ein stilles null, und .type() sagt dir, welcher gesetzt ist.

  • response.attributes() gibt nie null zurück. Mit ReturnValue.NONE gibt es eine DefaultSdkAutoConstructMap zurück, die leer, aber nicht null ist — eine Null-Prüfung greift also nie, und eine isEmpty()-Prüfung kann „der Dienst hat nichts geschickt" nicht von „das Item hat keine Attribute" unterscheiden. Das generierte hasAttributes() ist der Accessor, der den Unterschied kennt. Jedes Collection-Member in diesem SDK hat eins.

  • Der Builder typprüft alles außer dem, worauf es ankommt. updateExpression(String) akzeptiert jeden String; der Compiler kann SET nicht von einem Tippfehler unterscheiden, Expression-Fehler sind also 400er zur Laufzeit. ADD #upd2 :updValue2 ist das atomare Inkrement, eine conditionExpression von attribute_exists(Artist) macht den Aufruf zu reinem Aktualisieren, und die Grammatik steht in Update Expressions.

  • Bevorzuge die Expression gegenüber der alten attributeUpdates-Map. Ältere Beispiele zeigen sie noch; sie kann weder mehrere Klauseltypen noch Aliase noch eine Bedingung in einem Request ausdrücken.

  • getMessage() ist nicht die Nachricht des Dienstes. Das SDK hängt sein eigenes Transportdetail an:

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

    Logge das, wenn du die Request-ID für einen Supportfall willst. Vergleiche stattdessen über awsErrorDetails().errorCode() und nutze awsErrorDetails().errorMessage(), wenn du den blanken String willst.

Die Catch-Reihenfolge zählt hier mehr als sonst

ConditionalCheckFailedException extends DynamoDbException — ein zuerst platziertes catch (DynamoDbException e) verschluckt also genau den einen Fehlschlag, auf den du fast sicher verzweigen wolltest. Fang den spezifischen Typ zuerst ab und nimm gleich das Item mit:

} 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() ist hier false, und das ist richtig: Eine fehlgeschlagene Bedingung zu wiederholen scheitert einfach erneut.

Die Asymmetrie, die du dir merken solltest: ValidationException hat in diesem SDK keine Klasse. Durchsuch dynamodb-2.35.9.jar und es gibt nichts abzufangen. Ein reserviertes Wort, eine fehlerhafte Expression, ein Teilschlüssel: Sie alle kommen als schlichte DynamoDbException an, deren awsErrorDetails().errorCode() zufällig ValidationException lautet. In einer statisch typisierten Sprache ist das eine unangenehme Lücke, und sie bedeutet, dass Expression-Fehler zu String-Vergleichen zur Laufzeit werden.

Deshalb verdienen DynamoDBs reservierte Wörter einen Durchgang vor dem Ausliefern statt danach: Die Liste umfasst 573 Einträge und enthält Year, Name und Status — von denen keines in einer Java-Bean gefährlich aussieht. Um die rohe Tabelle statt der Bean-Abbildung darüber zu durchsuchen, lade DynoTable herunter.

Der Enhanced Client kann das nicht ausdrücken

Wenn dein übriger Datenzugriff über DynamoDbEnhancedClient und annotierte Beans läuft, ist diese Operation die eine, die dich auf DynamoDbClient zurückwirft. Reflection über UpdateItemEnhancedRequest.Builder fördert item, conditionExpression, ignoreNulls, ignoreNullsMode, returnValues, returnValuesOnConditionCheckFailure, returnConsumedCapacity und returnItemCollectionMetrics zutage. Es gibt keine Methode, die eine Update-Expression annimmt.

Die praktische Folge ist der atomare Zähler. ADD #upd2 :updValue2 inkrementiert Awards serverseitig ohne vorheriges Lesen; der Enhanced Client gibt dir eine gemappte Bean und ignoreNulls, um zu entscheiden, ob fehlende Felder entfernt werden — und nichts, was zu ADD kompiliert. Read-Modify-Write über eine Bean ist unter Nebenläufigkeit ein Lost-Update-Race, und genau das vermeidet das Snippet dieser Seite.

Verwandte Beispiele

Referenzen

Zuletzt verifiziert am 2026-07-28 gegen die oben verlinkte offizielle AWS-Dokumentation.

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.