DynamoDB PutItem em Java (AWS SDK v2)

PutItem grava um item inteiro e substitui qualquer item existente com a mesma chave primária (ações baseadas em item cobre como isso difere de UpdateItem). No AWS SDK for Java 2.x cada atributo entra em um PutItemRequest como um AttributeValue tipado, e o builder vai deixar você construir um que não tem como ser válido.

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

Explicação

AttributeValue.builder().build() compila. E também é inenviável. O builder não tem nenhum campo obrigatório, então um atributo em que você esqueceu o .s(...) passa perfeitamente na checagem de tipos e falha no serviço:

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

Esta é a forma específica em Java de um erro que outros SDKs tornam impossível: os types.AttributeValueMember* de Go são tipos separados, então não há nada para deixar sem definir. Veja "Supplied AttributeValue is empty" para a correção mais ampla.

Um null de Java entregue a .s(...) não vira um NULL do DynamoDB. Esta é a versão que morde de verdade, porque parece um valor:

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

Nenhum NullPointerException é lançado na construção. O builder simplesmente não registra nada, e a requisição falha com a mensagem Supplied AttributeValue is empty idêntica, apontando para um atributo do qual você nunca suspeitou. Se você quer um null de verdade, ele é AttributeValue.builder().nul(true).build(); na maioria das vezes o que você quer é omitir a entrada. Repare que isso é o oposto do SDK de Go, onde um ponteiro nil é marshalled para NULL e cria um atributo silenciosamente; ambos foram executados contra o mesmo motor no mesmo dia.

getMessage() não é a mensagem do serviço. O SDK acrescenta o seu próprio contexto, então a string fica:

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

Faça match em e.awsErrorDetails().errorCode() e leia e.awsErrorDetails().errorMessage() para obter o texto puro. Qualquer coisa que compare getMessage() com um literal quebra em um retry, que muda a contagem de tentativas.

Capture ConditionalCheckFailedException antes de DynamoDbException, e leia o que ela carrega. Ela estende DynamoDbException, então ordenar os blocos catch ao contrário torna o handler específico inalcançável. No objeto capturado: statusCode() retornou 400 e retryable() retornou false, que é a resposta honesta para uma rejeição de regra de negócio. Adicione .returnValuesOnConditionCheckFailure("ALL_OLD") à requisição e e.item() volta preenchido com o item que bloqueou a escrita (cinco atributos na execução acima, Year como AttributeValue(N=1994)), de modo que você não precisa de um getItem posterior para descobrir quem venceu.

Números entram como strings via .n(...). O tipo N do DynamoDB é texto decimal no protocolo, que é o que impede 1994 de virar um double. .n(String.valueOf(year)) é o idioma; não existe sobrecarga .n(int) para recorrer.

O client é Closeable, e de vida longa. O try-with-resources acima está certo para um programa de execução única e errado para um serviço: DynamoDbClient é dono de um pool de conexões HTTP e é thread-safe, então construa um por aplicação e deixe-o viver. Construir um por requisição é o bug de desempenho em Java mais comum nesta API.

Prefere beans a mapas de AttributeValue? O DynamoDB Enhanced Client (software.amazon.awssdk.enhanced.dynamodb) mapeia uma classe anotada direto para um item, o que elimina completamente a armadilha do builder vazio. Isso custa uma varredura reflexiva de TableSchema.fromBean na inicialização, que StaticTableSchema evita, se isso importar para você.

Faça isso visualmente

Toda falha acima começa com valores tipados construídos à mão. O conversor de JSON do DynamoDB gratuito pega JSON comum e devolve a forma tipada, para que você veja exatamente com o que o item deve parecer no protocolo antes de escrever um único AttributeValue.builder().

Para gravar e editar itens contra as suas próprias tabelas — um formulário por atributo, seletores de tipo, copiar o resultado de volta como Java — baixe o DynoTable.

Exemplos relacionados

Referências

Reproduzido em 2026-07-28 com o AWS SDK for Java 2.49.4 no OpenJDK 26.0.1, contra o DynamoDB Local (amazon/dynamodb-local) na porta 9000. O texto da exceção, o código de status e o conteúdo do item acima são saída capturada, copiada literalmente.

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.