UpdateItem do DynamoDB em Java (AWS SDK v2)

A atualização em si é uma chamada de builder. O que custa tempo aos desenvolvedores Java é tudo em volta dela: um objeto de resposta que nunca retorna null, uma hierarquia de exceções em que a falha interessante é subclasse daquela que você provavelmente capturou, e um cliente de alto nível que não consegue expressar esta operação de jeito nenhum.

Código

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

Explicação

  • AttributeValue.builder().n("1994") recebe uma String, e o mais curto AttributeValue.fromN("1994") também. Não existe sobrecarga n(int), porque números do DynamoDB guardam 38 dígitos significativos e nenhum primitivo do Java guarda. Na volta, attributes().get("Awards").n() também é uma String; o acessor do tipo errado retorna null em vez de lançar erro, então .s() em um número é um null silencioso, e .type() te diz qual deles está definido.

  • response.attributes() nunca retorna null. Com ReturnValue.NONE ele retorna um DefaultSdkAutoConstructMap que é vazio mas não nulo, então uma checagem de null nunca dispara e uma checagem de isEmpty() não consegue distinguir "o serviço não enviou nada" de "o item não tem atributos". O hasAttributes() gerado é o acessor que sabe a diferença. Todo membro de coleção neste SDK tem um.

  • O builder verifica os tipos de tudo, menos da parte que importa. updateExpression(String) aceita qualquer string; o compilador não distingue SET de um erro de digitação, então erros de expressão são 400s em tempo de execução. ADD #upd2 :updValue2 é o incremento atômico, uma conditionExpression de attribute_exists(Artist) torna a chamada somente-atualização, e a gramática está em expressões de atualização.

  • Prefira a expressão ao mapa legado attributeUpdates. Exemplos mais antigos ainda o mostram; ele não consegue expressar múltiplos tipos de cláusula, aliases ou uma condição em uma única requisição.

  • getMessage() não é a mensagem do serviço. O SDK acrescenta seus próprios detalhes de transporte:

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

    Logue isso se você quiser o request ID para um chamado de suporte. Compare por awsErrorDetails().errorCode() em vez disso, e use awsErrorDetails().errorMessage() quando quiser a string pura.

A ordem dos catches importa mais que o normal aqui

ConditionalCheckFailedException extends DynamoDbException, então um catch (DynamoDbException e) colocado primeiro engole justamente a falha na qual você quase certamente queria ramificar. Capture o tipo específico primeiro, e aproveite para pegar o item:

} 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() é false neste caso, o que está correto: tentar de novo uma condição que falhou apenas falha de novo.

A assimetria a lembrar é que ValidationException não tem classe neste SDK. Procure no dynamodb-2.35.9.jar e não há nada para capturar. Uma palavra reservada, uma expressão malformada, uma chave parcial: todas chegam como um DynamoDbException comum cujo awsErrorDetails().errorCode() por acaso diz ValidationException. Em uma linguagem estaticamente tipada isso é uma lacuna incômoda, e significa que erros de expressão viram comparações de string em tempo de execução.

É por isso que as palavras reservadas do DynamoDB merecem uma passada antes de você entregar, e não depois: a lista chega a 573 entradas e inclui Year, Name e Status, nenhuma das quais parece perigosa em um bean Java. Para navegar pela tabela crua em vez do bean mapeado por cima dela, baixe o DynoTable.

O enhanced client não consegue expressar isso

Se o resto do seu acesso a dados passa pelo DynamoDbEnhancedClient e beans anotados, esta operação é a que te joga de volta para o DynamoDbClient. A reflexão sobre UpdateItemEnhancedRequest.Builder revela item, conditionExpression, ignoreNulls, ignoreNullsMode, returnValues, returnValuesOnConditionCheckFailure, returnConsumedCapacity e returnItemCollectionMetrics. Não há método que aceite uma expressão de atualização.

A consequência prática é o contador atômico. ADD #upd2 :updValue2 incrementa Awards no lado do servidor sem leitura prévia; o enhanced client te dá um bean mapeado e ignoreNulls para decidir se campos ausentes são removidos, e nada que compile para ADD. Ler-modificar-escrever através de um bean é uma corrida de atualização perdida sob concorrência, que é exatamente o que o trecho desta página evita.

Exemplos relacionados

Referências

Verificado pela última vez em 2026-07-28 contra a documentação oficial da AWS vinculada acima.

Trabalhe com o DynamoDB sem o Console

Um cliente desktop rápido para DynamoDB que roda o SQL de verdade que o DynamoDB não consegue — JOINs, GROUP BY, agregações — com edição visual e um agente de IA com suas próprias chaves do Bedrock.

Teste grátis de 30 dias, sem cartão de crédito — depois o plano Grátis sem limite de tempo.