Java(AWS SDK v2)での DynamoDB PutItem
PutItem はアイテムを丸ごと書き込み、同じプライマリキーを持つ既存のアイテムを置き換えます(UpdateItem との違いはアイテム単位の操作で扱っています)。AWS SDK for Java 2.x では、すべての属性が型付きの AttributeValue として PutItemRequest に入り、そしてビルダーは、どうやっても妥当になりえないものの構築を許してしまいます。
コード
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());
}
}
}解説
AttributeValue.builder().build() はコンパイルが通ります。そして送信できません。ビルダーに必須フィールドは無いので、.s(...) を忘れた属性は型チェックを完璧に通過し、サービス側で失敗します。
DynamoDbException | ValidationException | Supplied AttributeValue is empty, must contain exactly one of the supported datatypesこれは、他の SDK では起こしようがない誤りの Java 固有の形です。Go の types.AttributeValueMember* は別々の型なので、未設定にしておけるものがありません。より広い対処法は"Supplied AttributeValue is empty"を参照してください。
.s(...) に渡した Java の null は、DynamoDB の NULL にはなりません。実際に刺さるのはこちらのバージョンです。値のように見えるからです。
AttributeValue.builder().s(customer.getNotes()).build() // getNotes() returned null構築時に NullPointerException は送出されません。ビルダーは単に何も記録せず、リクエストはまったく同じ Supplied AttributeValue is empty というメッセージで失敗し、疑ってもいなかった属性を指し示します。本物の null が欲しいなら AttributeValue.builder().nul(true).build() です。もっと多いのは、そのエントリ自体を省きたい場合でしょう。これは Go SDK とは逆である点に注意してください。あちらでは nil ポインタが NULL にマーシャルされ、黙って属性を作ります。両者は同じ日に同じエンジンに対して実行しました。
getMessage() はサービスのメッセージではありません。SDK が独自のコンテキストを付け足すので、文字列はこうなります。
The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 77a08ef1-a3a9-4f97-b309-4cb0741edd1a) (SDK Attempt Count: 1)e.awsErrorDetails().errorCode() でマッチし、素のテキストは e.awsErrorDetails().errorMessage() で読みましょう。getMessage() をリテラルと比較するものは、試行回数を変えるリトライ 1 回で壊れます。
ConditionalCheckFailedException を DynamoDbException より先に捕捉し、それが携えているものを読みましょう。これは DynamoDbException を継承しているので、catch ブロックを逆順に並べると個別のハンドラーに到達できなくなります。捕捉したオブジェクトでは、statusCode() が 400 を、retryable() が false を返しました。ビジネスロジック上の拒否に対する正直な答えです。リクエストに .returnValuesOnConditionCheckFailure("ALL_OLD") を足すと、e.item() が書き込みを阻んだアイテムを中身入りで返します(上の実行では属性 5 つ、Year は AttributeValue(N=1994))。誰が勝ったのかを知るために追加の getItem を打つ必要はありません。
数値は .n(...) を通して文字列として入ります。DynamoDB の N 型はワイヤ上では 10 進のテキストであり、それが 1994 を double にさせない仕組みです。イディオムは .n(String.valueOf(year)) で、手を伸ばせる .n(int) のオーバーロードはありません。
クライアントは Closeable で、かつ長命です。上の try-with-resources は一発ものプログラムには正しく、サービスには誤りです。DynamoDbClient は HTTP コネクションプールを所有し、スレッドセーフなので、アプリケーションごとに 1 つ作って生かしておきましょう。リクエストごとに作るのが、この API で最もよくある Java のパフォーマンスバグです。
AttributeValue のマップより Bean を選びたい場合 — DynamoDB Enhanced Client(software.amazon.awssdk.enhanced.dynamodb)は、アノテーションを付けたクラスをアイテムへ直接マッピングし、空ビルダーの罠を丸ごと取り除きます。代償は起動時のリフレクションによる TableSchema.fromBean のスキャンで、それが気になるなら StaticTableSchema で回避できます。
ビジュアルに行う
上のどの失敗も、型付きの値を手で組み立てるところから始まります。無料の DynamoDB JSON コンバーターは、普通の JSON を受け取って型付きの形を返すので、AttributeValue.builder() を 1 行も書く前に、アイテムがワイヤ上でどう見えるべきかを正確に確認できます。
自分のテーブルに対してアイテムを書き込み・編集するには — 属性ごとのフォーム、型ピッカー、結果の Java としてのコピー — DynoTable をダウンロードしてください。
関連する例
- Go での DynamoDB PutItem — AWS SDK for Go v2 による同じ条件付き書き込み。nil ポインタは逆向きに失敗します。
- Java での DynamoDB UpdateItem — アイテムを置き換えるのではなく、特定の属性を変更します。
- DynamoDB の条件式 —
attribute_not_exists、楽観ロック、ほか。 - DynamoDB ConditionalCheckFailedException — アイテムが既に存在するとき、作成専用の条件が送出するもの。
- DynamoDB ValidationException — 不正なアイテムや式に対する総括的なエラー。
参考資料
- PutItem — Amazon DynamoDB API Reference
- Use PutItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- DynamoDbClient — AWS SDK for Java 2.x API Reference
- AttributeValue — AWS SDK for Java 2.x API Reference
- PutItemRequest — AWS SDK for Java 2.x API Reference
- Condition expressions — Amazon DynamoDB Developer Guide
2026-07-28 に、OpenJDK 26.0.1 上の AWS SDK for Java 2.49.4 で、ポート 9000 の DynamoDB Local(amazon/dynamodb-local)に対して再現しました。上記の例外テキスト、ステータスコード、アイテムの内容は、取得した出力をそのまま逐語で写したものです。