UpdateItem de DynamoDB en Java (SDK de AWS v2)

La actualización en sí es una sola llamada al builder. Lo que le cuesta tiempo a un desarrollador Java es todo lo que la rodea: un objeto de respuesta que nunca devuelve null, una jerarquía de excepciones donde el fallo interesante es una subclase de la que probablemente capturaste, y un cliente de alto nivel que no puede expresar esta operación en absoluto.

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

Explicación

  • AttributeValue.builder().n("1994") recibe un String, y lo mismo el más corto AttributeValue.fromN("1994"). No hay sobrecarga n(int), porque los números de DynamoDB soportan 38 dígitos significativos y ningún primitivo de Java lo hace. A la vuelta, attributes().get("Awards").n() también es un String; el accesor del tipo equivocado devuelve null en vez de lanzar, así que .s() sobre un número es un null silencioso, y .type() te dice cuál está puesto.

  • response.attributes() nunca devuelve null. Con ReturnValue.NONE devuelve un DefaultSdkAutoConstructMap vacío pero no nulo, así que una comprobación de null nunca se dispara y una comprobación de isEmpty() no puede distinguir «el servicio no envió nada» de «el Item no tiene atributos». El hasAttributes() generado es el accesor que sabe la diferencia. Cada miembro de colección de este SDK tiene uno.

  • El builder comprueba los tipos de todo salvo de la parte que importa. updateExpression(String) acepta cualquier cadena; el compilador no distingue SET de una errata, así que los fallos de expresión son 400 en tiempo de ejecución. ADD #upd2 :updValue2 es el incremento atómico, una conditionExpression de attribute_exists(Artist) hace que la llamada sea solo de actualización, y la gramática está en expresiones de actualización.

  • Prefiere la expresión al mapa heredado attributeUpdates. Los ejemplos antiguos todavía lo muestran; no puede expresar varios tipos de cláusula, ni alias, ni una condición en una sola petición.

  • getMessage() no es el mensaje del servicio. El SDK le añade su propio detalle de transporte:

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

    Registra eso si quieres el ID de petición para un caso de soporte. Compara mejor sobre awsErrorDetails().errorCode(), y usa awsErrorDetails().errorMessage() cuando quieras la cadena a secas.

Aquí el orden de captura importa más de lo normal

ConditionalCheckFailedException extends DynamoDbException, así que un catch (DynamoDbException e) puesto primero se traga el único fallo sobre el que casi con seguridad querías ramificar. Captura primero el tipo específico y, ya que estás, quédate con el 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() es false en este caso, y es correcto: reintentar una condición fallida solo vuelve a fallar.

La asimetría que hay que recordar es que ValidationException no tiene clase en este SDK. Busca en dynamodb-2.35.9.jar y no hay nada que capturar. Una palabra reservada, una expresión mal formada, una clave parcial: todas llegan como un DynamoDbException corriente cuyo awsErrorDetails().errorCode() resulta ser ValidationException. En un lenguaje de tipado estático eso es un hueco chocante, y significa que los fallos de expresión son comparaciones de cadenas en tiempo de ejecución.

Por eso las palabras reservadas de DynamoDB merecen una pasada antes de publicar y no después: la lista llega a 573 entradas e incluye Year, Name y Status, ninguna de las cuales parece peligrosa en un bean de Java. Para explorar la tabla en bruto en vez del bean mapeado encima, descarga DynoTable.

El enhanced client no puede expresar esto

Si el resto de tu acceso a datos pasa por DynamoDbEnhancedClient y beans anotados, esta operación es la que te devuelve a DynamoDbClient. Reflexionar sobre UpdateItemEnhancedRequest.Builder saca item, conditionExpression, ignoreNulls, ignoreNullsMode, returnValues, returnValuesOnConditionCheckFailure, returnConsumedCapacity y returnItemCollectionMetrics. No hay ningún método que acepte una expresión de actualización.

La consecuencia práctica es el contador atómico. ADD #upd2 :updValue2 incrementa Awards en el servidor sin leer antes; el enhanced client te da un bean mapeado e ignoreNulls para decidir si los campos ausentes se eliminan, y nada que compile a ADD. Leer-modificar-escribir a través de un bean es una carrera de actualización perdida bajo concurrencia, que es precisamente lo que evita el fragmento de esta página.

Ejemplos relacionados

Referencias

Verificado por última vez el 2026-07-28 contra la documentación oficial de AWS enlazada arriba.

Trabaja con DynamoDB sin la Consola

Un cliente de escritorio rápido para DynamoDB que ejecuta el SQL real que DynamoDB no puede — JOINs, GROUP BY, agregaciones — con edición visual y un agente de IA con tus propias claves de Bedrock.

Prueba gratuita de 30 días, sin tarjeta — después, el plan Free sin límite de tiempo.