JavaでAWSを使おう!AWS SDK for Javaの紹介と始め方

JavaでAWSを使おう!AWS SDK for Javaの紹介と始め方

1. はじめに:クラウドとJavaの強力な組み合わせ

現代のソフトウェア開発において、クラウドコンピューティングは欠かせない存在となりました。アジリティ、スケーラビリティ、コスト効率といったメリットを享受するために、多くの企業や開発者がAmazon Web Services (AWS)のようなクラウドプラットフォームを利用しています。

一方、プログラミング言語の世界では、Javaは長年にわたりエンタープライズアプリケーション開発の中心的な言語としての地位を確立しています。その堅牢性、豊富なライブラリ、大規模開発への適性から、多くのシステムで採用されています。

JavaアプリケーションがAWS上で動作する場合、アプリケーションからS3にファイルを保存したり、DynamoDBからデータを読み書きしたり、SQSにメッセージを送信したりといった、AWSの各種サービスをプログラムから操作する必要が出てきます。このようなニーズに応えるのが、「AWS SDK for Java」です。

1.1. クラウド時代のJava開発

クラウド環境でのJavaアプリケーション開発は、従来のオンプレミス環境とは異なるアプローチを必要とします。マイクロサービスアーキテクチャの採用、サーバーレスコンピューティング(AWS Lambdaなど)の活用、コンテナ技術(Docker, Kubernetes, ECS, EKS)の利用などが一般的になり、アプリケーションは動的に変化するインフラストラクチャに適応する必要があります。

このような環境で、JavaアプリケーションがAWSの各種リソースと効率的かつ安全に連携するためには、専用のツールキットが不可欠です。それがAWS SDK for Javaなのです。

1.2. AWS SDK for Javaとは?

AWS SDK for Javaは、JavaアプリケーションからAWSのサービスを簡単に操作できるようにするための公式ライブラリです。AWSが提供する何百ものサービス(S3、EC2、Lambda、DynamoDB、SQS、SNSなど)のAPIをJavaオブジェクトとして抽象化し、開発者がAWSのREST APIの詳細を知らなくても、Javaコードから直感的に呼び出せるように設計されています。

SDKの主な役割は以下の通りです。

  • API呼び出しの簡素化: 各サービスAPIに対応するJavaクラスやメソッドを提供します。
  • リクエスト/レスポンスのシリアライゼーション/デシリアライゼーション: JavaオブジェクトとHTTPリクエスト/レスポンス間のデータ変換を自動で行います。
  • 認証情報の管理: AWSアカウントへのアクセスに必要な認証情報(アクセスキー、IAMロールなど)を安全に管理し、自動的にAPIリクエストに付与します。
  • エラー処理: AWSからのエラーレスポンスをキャッチし、Javaの例外として報告します。
  • リトライメカニズム: 一時的なネットワークの問題などでAPI呼び出しが失敗した場合に、自動的にリトライを行います。
  • 様々なHTTPクライアントのサポート: アプリケーションのニーズに応じて、異なるHTTPクライアントライブラリを選択できます。
  • 非同期処理のサポート: ノンブロッキングI/Oを使った非同期API呼び出しをサポートし、高性能なアプリケーション構築を支援します。

AWS SDK for Javaを利用することで、開発者はインフラストラクチャの詳細に煩わされることなく、アプリケーションのビジネスロジックに集中できます。

1.3. 本記事の目的と対象読者

本記事は、Java開発者やアーキテクトの方々が、AWS SDK for Java v2 (最新バージョン) を利用してAWSサービスを操作できるようになるための、詳細な導入ガイドとなることを目指します。

具体的には、以下の内容を網羅します。

  • AWS SDK for Java v2の概要とv1からの進化点
  • プロジェクトへのSDKの追加方法(Maven/Gradle)
  • AWS認証情報の設定方法(最も重要なステップの一つ)
  • 主要なAWSサービス(S3, DynamoDB, SQS, Lambda, EC2など)を操作するための具体的なコード例
  • SDKの高度な機能(非同期処理、リトライ、Pager, Waitersなど)
  • エラーハンドリングとデバッグの基本

対象読者は、Javaでの開発経験があり、これからAWSサービスをJavaアプリケーションから利用したいと考えている方、またはすでにSDK v1を使っているがv2に移行したいと考えている方です。

本記事を読むことで、AWS SDK for Java v2を使ったAWS連携の基礎をしっかりと理解し、自身のJavaプロジェクトでAWSサービスを自信を持って利用できるようになることを願っています。

それでは、AWS SDK for Java v2の世界へ踏み込みましょう!

2. AWS SDK for Java v2 の世界へようこそ

AWS SDK for Javaは現在、メジャーバージョンとしてv1とv2が存在します。公式にはv2が推奨されており、新しい開発ではv2を利用することが強く推奨されます。v2はv1と比較して、パフォーマンス、使いやすさ、拡張性において多くの改善が施されています。

2.1. v1からの進化点:なぜv2を選ぶべきか

AWS SDK for Java v2 (正式名称: AWS SDK for Java 2.x) は、2018年7月にGeneral Availability (GA) となり、従来のv1 (1.x) から大幅なアーキテクチャ変更が行われました。v2が登場した背景には、v1の課題、特にAWSサービスの増加に伴うライブラリサイズの肥大化や、非同期処理のネイティブサポートへのニーズがありました。

v2の主な進化点は以下の通りです。

  • モジュール性: v1は単一の大きなJARファイルに全サービスのクライアントが含まれていましたが、v2はサービスごとに個別のモジュールに分割されています。これにより、必要なサービスのモジュールだけを依存関係に追加でき、アプリケーションのビルドサイズを大幅に削減できます。
  • 非同期処理のネイティブサポート: v2は非同期クライアントを最初からサポートしており、Java 8のCompletableFutureを活用したノンブロッキングI/Oによる効率的な処理が可能です。これにより、多数のAPI呼び出しを同時に行うようなシナリオで高いパフォーマンスを発揮できます。v1でも非同期クライアントはありましたが、v2の方がより統合され、使いやすくなっています。
  • 使いやすいAPI: v2はビルダーパターンを多用し、Immutable (不変) なリクエストオブジェクトや設定オブジェクトを生成します。これにより、コードの可読性が向上し、スレッドセーフになります。
  • 柔軟なHTTPクライアント: v2では、Nettyベースの非同期HTTPクライアント、Apache HttpClientベースの同期HTTPクライアント、そしてJDK標準のHttpClientなど、複数のHTTPクライアント実装を選択できます。アプリケーションの特性に合わせて最適なクライアントを選択できます。
  • POJOマッピングの強化: DynamoDB Enhanced Clientなど、特定のサービスではPOJO (Plain Old Java Object) を直接マッピングできる機能が強化され、データ操作がより直感的になりました。
  • 設定の一貫性: クライアント設定(リージョン、認証情報、リトライなど)が一貫した方法で行えるようになりました。
  • パフォーマンスの向上: 非同期処理や最適化された内部処理により、全体的なパフォーマンスが向上しています。

これらの理由から、新規開発はもちろん、既存のv1アプリケーションでもv2への移行を検討する価値は十分にあります。

2.2. v2の主要な特徴

v2のアーキテクチャと設計思想に基づいた、主要な特徴をさらに詳しく見ていきましょう。

2.2.1. モジュール性

v2 SDKはコアライブラリと、各サービスごとの独立したモジュールで構成されています。例えば、S3クライアントを使いたい場合は、コアライブラリに加えてS3モジュールを依存関係に追加します。

“`xml


software.amazon.awssdk
aws-core
2.x.y


software.amazon.awssdk
s3
2.x.y


software.amazon.awssdk
dynamodb
2.x.y

“`

このモジュール構造により、アプリケーションに必要なコードだけを含めることができるため、デプロイメントパッケージのサイズを小さく保つことができます。これは特に、AWS Lambdaのようなサーバーレス環境や、コンテナイメージサイズを最適化したい場合に大きなメリットとなります。

2.2.2. 非同期サポートとノンブロッキングI/O

v2 SDKの非同期クライアントは、NettyのようなノンブロッキングI/Oフレームワークをバックエンドに使用しています。API呼び出しは即座にFutureオブジェクト(Java 8ではCompletableFuture)を返し、実際のネットワーク通信や処理はバックグラウンドで行われます。

これにより、API呼び出しの完了を待たずに次の処理に進めるため、スレッドをブロッキングすることなく多数の同時リクエストを効率的に処理できます。これは、高スループットが求められるアプリケーションや、多数のAWSサービスと同時に連携するアプリケーションにとって非常に重要です。

非同期クライアントの作成例:

“`java
import software.amazon.awssdk.services.s3.S3AsyncClient;

S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
.region(Region.US_EAST_1)
.build();
“`

非同期操作の例(S3へのオブジェクトアップロード):

“`java
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import java.util.concurrent.CompletableFuture;

PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(“my-bucket”)
.key(“my-file.txt”)
.build();

CompletableFuture future = s3AsyncClient.putObject(putObjectRequest, RequestBody.fromString(“Hello, S3!”));

future.whenComplete((response, error) -> {
if (error != null) {
error.printStackTrace();
} else {
System.out.println(“Object uploaded successfully. ETag: ” + response.eTag());
}
});

// 必要であればfuture.join()などで完了を待つ
// future.join();
“`

2.2.3. ビルダーパターンとIMMUTABLEなオブジェクト

v2 SDKのほとんどのオブジェクト(クライアント、リクエスト、レスポンスなど)は、ビルダーパターンを使って構築され、一度作成されると内容が変更できないImmutableなオブジェクトです。

例:S3クライアントのビルド

java
S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1) // リージョンの設定
.credentialsProvider(ProfileCredentialsProvider.create("my-profile")) // 認証情報プロバイダの設定
.httpClientBuilder(ApacheHttpClient.builder() // HTTPクライアントの設定
.proxyForRequests(ProxyConfiguration.builder()
.host("localhost").port(8080).build()))
.overrideConfiguration(ClientOverrideConfiguration.builder() // その他設定
.apiCallTimeout(Duration.ofSeconds(5))
.retryPolicy(RetryPolicy.builder().maxAttempts(3).build())
.build())
.build(); // クライアントオブジェクトの構築

このパターンにより、オブジェクトの生成が明確になり、設定オプションが多い場合でもコードが読みやすくなります。また、Immutableであるため、複数のスレッドで同じオブジェクトを安全に共有できます。

2.2.4. POJOベースのマッピング

DynamoDB Enhanced Clientなどは、JavaのPOJOとDynamoDBのアイテムを簡単にマッピングできます。これは、アノテーションを使用することで実現され、データアクセスコードを大幅に削減できます。

例:UserクラスとDynamoDBテーブルのマッピング

“`java
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;

@DynamoDbBean
public class User {
private String userId;
private String name;
private String email;

@DynamoDbPartitionKey
public String getUserId() {
    return userId;
}

public void setUserId(String userId) {
    this.userId = userId;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

}
“`

このPOJOを使ってDynamoDBとデータのやり取りができます。

2.2.5. HTTPクライアントの選択肢

v2 SDKでは、バックエンドで使用するHTTPクライアントを自由に選択できます。

  • NettyNioAsyncHttpClient: デフォルトの非同期クライアント。Nettyベースで高性能なノンブロッキングI/Oを提供します。
  • ApacheHttpClient: デフォルトの同期クライアント。Apache HttpClientベースで、同期的なブロッキングI/Oを使用します。
  • UrlConnectionHttpClient: JDKのHttpURLConnectionを使ったシンプルな同期クライアント。追加の依存関係が不要なのが利点です。
  • JdkHttpClient: Java 11で導入されたJDK標準のjava.net.http.HttpClientを使った非同期/同期クライアント。

特定のニーズ(例: プロキシ設定の詳細な制御、HTTP/2サポートなど)に応じて、最適なクライアントを選択し、ビルダーで指定できます。

2.3. v2のアーキテクチャ概要

v2 SDKは、大きく分けて以下のレイヤーで構成されています。

  1. サービスクライアントレイヤー: 各AWSサービスに対応するクライアントインターフェースと実装クラスを提供します。開発者は主にこのレイヤーのクラス(例: S3Client, DynamoDbClient)を使用します。同期クライアントと非同期クライアントの両方が提供されます。
  2. プロトコルレイヤー: 各サービスのAPIが使用する通信プロトコル(REST/JSON, REST/XML, AWS Query, AWS JSON 1.1など)を処理します。リクエスト/レスポンスのシリアライズ/デシリアライズを行います。
  3. HTTPクライアントレイヤー: 実際のHTTP通信を行います。前述の通り、複数の実装を選択可能です。認証情報の付与、リトライ処理、エラー処理などもこの層やその周辺で行われます。
  4. 共通ライブラリ: ロギング、認証情報プロバイダ、リージョン解決、構成設定などの共通機能を提供します。

このアーキテクチャにより、各レイヤーが独立しており、例えばHTTPクライアントの実装を変更しても、サービスクライアントレイヤーのコードに影響を与えずに済みます。これにより、SDK全体の柔軟性と保守性が向上しています。

3. 開発環境の準備とSDKの導入

AWS SDK for Java v2を使い始める前に、開発環境を準備し、プロジェクトにSDKライブラリを追加する必要があります。

3.1. 前提条件:Java開発環境のセットアップ

AWS SDK for Java v2を利用するためには、以下のものが必要です。

  • Java Development Kit (JDK): Java 8以降が必要です。AWS Lambdaなど特定のサービスでは、より新しいJDKバージョン(Corretto 11, Corretto 17など)が推奨または必須となる場合があります。ご自身のターゲット環境に合わせてJDKを選択してください。OpenJDKディストリビューション(Amazon Corretto, Adoptium Temurinなど)が一般的に使用されます。
  • ビルドツール: Javaプロジェクトの依存関係管理とビルドには、MavenまたはGradleを使用するのが一般的です。これらのツールがインストールされ、プロジェクトで使える状態になっていることを確認してください。
  • IDE (Optional but Recommended): Eclipse, IntelliJ IDEA, VS Codeなど、使い慣れた統合開発環境があると開発効率が向上します。

これらのツールがインストールされていることを確認してください。ターミナルでjava -versionmvn --version, gradle --versionを実行してバージョン情報が表示されればOKです。

3.2. プロジェクトへのSDKの追加

AWS SDK for Java v2は、前述の通りサービスごとにモジュールが分割されています。利用したいAWSサービスのモジュールをプロジェクトの依存関係に追加します。

3.2.1. Mavenを使用する場合

プロジェクトのpom.xmlファイルを開き、<dependencies>セクションにSDKの依存関係を追加します。まず、SDKの共通依存関係(aws-coreなど、これは通常個別のサービスモジュールに含まれますが、明示的に追加しても問題ありません)と、利用したいサービスモジュールを追加します。バージョン番号は、記事執筆時点の最新安定版またはプロジェクトで定められたバージョンを指定してください。最新バージョンはMaven CentralリポジトリやAWS SDK for Javaの公式ドキュメントで確認できます。

例:S3とDynamoDBを利用する場合

“`xml 2.20.162




software.amazon.awssdk
bom
${aws.sdk.version}
pom
import

    <!-- S3サービスモジュール -->
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3</artifactId>
    </dependency>

    <!-- DynamoDBサービスモジュール -->
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>dynamodb</artifactId>
    </dependency>

    <!-- DynamoDB Enhanced Client (POJOマッピング用) -->
     <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>dynamodb-enhanced</artifactId>
    </dependency>

    <!-- 必要に応じて他のサービスモジュールを追加 -->
    <!-- <dependency><groupId>software.amazon.awssdk</groupId><artifactId>lambda</artifactId></dependency> -->
    <!-- <dependency><groupId>software.amazon.awssdk</groupId><artifactId>sqs</artifactId></dependency> -->

    <!-- ロギングの実装を追加 (SDK内部でSlf4jを使用) -->
    <!-- 例: Logbackを使用する場合 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.7</version> <!-- 最新バージョンを指定 -->
    </dependency>

    ...
</dependencies>
...

“`

ポイント:

  • bom (Bill of Materials) を<dependencyManagement>セクションでimportスコープで使用すると、個別のSDKモジュール <dependency> ではバージョン番号の指定が不要になります。これは、互換性のあるSDKモジュールのバージョンセットを管理するための便利な方法です。
  • SDK内部ではロギングにSlf4jを使用しています。Slf4jは抽象化レイヤーなので、Logback, Log4j2, java.util.loggingなど、実際のロギング実装を別途依存関係に追加する必要があります。上記はLogbackを追加する例です。

pom.xmlを保存したら、Mavenプロジェクトをリロードするか、mvn clean install(またはmvn dependency:resolve)を実行して依存関係をダウンロードしてください。

3.2.2. Gradleを使用する場合

プロジェクトのbuild.gradleファイルを開き、dependenciesブロックにSDKの依存関係を追加します。Mavenと同様に、BOMを使用してバージョン管理を簡素化できます。

例:S3とDynamoDBを利用する場合 (Groovy DSL)

“`gradle
plugins {
id ‘java’
}

group ‘com.example’
version ‘1.0-SNAPSHOT’

repositories {
mavenCentral()
}

// SDKのバージョンを指定
def awsSdkVersion = ‘2.20.162’ // 例: 最新バージョンを指定

dependencies {
// AWS SDK for Java v2 BOM (Bill of Materials)
// platform()を使うと、各SDKモジュールのバージョン指定を省略できます
implementation platform(“software.amazon.awssdk:bom:${awsSdkVersion}”)

// S3サービスモジュール
implementation 'software.amazon.awssdk:s3'

// DynamoDBサービスモジュール
implementation 'software.amazon.awssdk:dynamodb'

// DynamoDB Enhanced Client (POJOマッピング用)
implementation 'software.amazon.awssdk:dynamodb-enhanced'

// 必要に応じて他のサービスモジュールを追加
// implementation 'software.amazon.awssdk:lambda'
// implementation 'software.amazon.awssdk:sqs'

// ロギングの実装を追加 (SDK内部でSlf4jを使用)
// 例: Logbackを使用する場合
implementation 'ch.qos.logback:logback-classic:1.4.7' // 最新バージョンを指定

// JUnitなどのテスト依存関係もここに追加
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' // 例
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' // 例

}

test {
useJUnitPlatform()
}
“`

build.gradleを保存したら、Gradleプロジェクトをリロードするか、gradle build(またはgradle dependencies)を実行して依存関係をダウンロードしてください。

3.2.3. 特定のサービスSDKを追加する

上記の例で示したように、特定のAWSサービスと連携するには、そのサービスのモジュールを依存関係に追加します。モジュールのArtifact IDは通常、サービス名の小文字に対応します。

  • Amazon S3: s3
  • Amazon EC2: ec2
  • AWS Lambda: lambda
  • Amazon DynamoDB: dynamodb (POJOマッピングには dynamodb-enhanced も必要)
  • Amazon SQS: sqs
  • Amazon SNS: sns
  • Amazon RDS: rds
  • Amazon CloudWatch: cloudwatch
  • など多数…

必要なサービスモジュールを正確に把握し、最小限の依存関係に留めることで、アプリケーションのフットプリントを最適化できます。

3.3. 最初のステップ:基本的なSDKクライアントの作成

SDKをプロジェクトに追加したら、AWSサービスと通信するためのクライアントオブジェクトを作成します。クライアントは通常、サービス名に対応するクラス名(例: S3Client, DynamoDbClient)を持ち、そのbuilder()メソッドを使って構築します。

例:S3クライアントの作成(最もシンプル)

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

// クライアントの作成
S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1) // 使用するAWSリージョンを指定
.build();

// クライアントを使った操作(次のセクションで詳しく説明)
// ListBucketsResponse response = s3Client.listBuckets();

// クライアントの使用が終わったらクローズする (try-with-resourcesが便利)
s3Client.close();
“`

この例では、Region.US_EAST_1というリージョンを指定しています。SDKはデフォルトで認証情報を自動的に解決しようとします(次のセクションで詳しく説明)。

ほとんどの場合、リージョンは必須の設定項目です。アプリケーションがデプロイされる環境に応じて、適切なリージョンを指定する必要があります。リージョンは環境変数、システムプロパティ、または共有設定ファイルからも読み込むことができますが、明示的にコードで指定することも可能です。

クライアントオブジェクトはスレッドセーフです。通常、アプリケーション内で一度作成し、複数のスレッドで共有することが推奨されます。ただし、設定(リージョン、認証情報、リトライ戦略など)が異なるクライアントが必要な場合は、別途インスタンスを作成します。

4. AWS認証情報の設定:SDKがAWSと対話する方法

AWS SDKがAWSサービスと通信するためには、認証情報(Credentials)が必要です。これにより、誰が(どのAWSアカウント/IAMユーザー/IAMロール)どのような権限で操作しようとしているのかをAWSが識別し、リクエストを許可または拒否します。

認証情報の設定は、AWS SDK for Javaを利用する上で最も重要かつ最初のステップです。適切に設定されていないと、SDKはAPI呼び出しを行うことができません。

AWS SDK for Java v2は、複数の方法で認証情報を取得できます。SDKはこれらの方法を定義された優先順位に従って順番に探しに行き、最初に見つかった有効な認証情報を使用します。

4.1. AWS認証情報の種類と優先順位

SDKが認証情報を探しに行くデフォルトの順番は以下の通りです。

  1. JVMシステムプロパティ: aws.accessKeyId および aws.secretAccessKey システムプロパティ。
    例: java -Daws.accessKeyId=YOUR_ACCESS_KEY -Daws.secretAccessKey=YOUR_SECRET_KEY YourApp
  2. 環境変数: AWS_ACCESS_KEY_ID および AWS_SECRET_ACCESS_KEY 環境変数。STSセッショントークンを使用する場合は AWS_SESSION_TOKEN も。
  3. Web Identity Tokenファイル: コンテナ環境(EKSサービスアカウントなど)で使用される認証情報。AWS_WEB_IDENTITY_TOKEN_FILE 環境変数などで指定されたパスのファイル。
  4. 共有認証情報ファイル: デフォルトの場所 (~/.aws/credentials または %USERPROFILE%\.aws\credentials on Windows) にある設定ファイル。プロファイル名 ([default], [my-profile]) を指定して認証情報を格納できます。
  5. 共有設定ファイル: デフォルトの場所 (~/.aws/config または %USERPROFILE%\.aws\config on Windows) にある設定ファイル。認証情報だけでなく、デフォルトリージョンや出力フォーマットなどの設定も含まれます。このファイルでもプロファイルを指定して認証情報を定義できます(credential_sourcerole_arnを使用)。
  6. Credential Provider Chain (EC2/ECS/Lambda):
    • AWS ECSコンテナからの認証情報: ECSタスクにIAMロールが割り当てられている場合、SDKはコンテナエージェントのAPIから一時的な認証情報を取得します。AWS_CONTAINER_CREDENTIALS_RELATIVE_URI 環境変数で指定されます。
    • AWS EC2インスタンスからの認証情報: EC2インスタンスにIAMロールが割り当てられている場合、SDKはインスタンスメタデータサービス (IMDS) から一時的な認証情報を取得します。
    • AWS Lambda実行環境からの認証情報: Lambda関数に実行ロールが割り当てられている場合、SDKは自動的にそのロールの一時的な認証情報を使用します。

この優先順位を理解しておくことは非常に重要です。例えば、環境変数にアクセスキー/シークレットキーを設定している場合、共有認証情報ファイルの設定よりも優先されます。

セキュリティの観点から、開発環境以外(ステージング、本番環境など)では、ハードコーディングされたアクセスキー/シークレットキーや、システムプロパティ/環境変数に平文で認証情報を置くことは絶対に避けるべきです。EC2/ECS/LambdaなどのAWSサービス上でアプリケーションを動作させる場合は、IAMロールを割り当て、SDKに自動的に一時的な認証情報を取得させる方法が最も安全で推奨される方法です。ローカル開発環境では、共有認証情報ファイルを使用するのが一般的です。

4.2. 共有認証情報ファイル (~/.aws/credentials) の設定

ローカル開発環境で最も一般的に使用される方法です。ホームディレクトリの.awsサブディレクトリにcredentialsという名前のファイルを作成し、以下のフォーマットで認証情報を記述します。

“`ini
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJairXufRdECFEXAMPLEKEY
aws_session_token = OptionallyIncludeThis

[my-profile]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = sezH4yZEXAMPLEKEY
“`

  • [default] セクションは、明示的にプロファイルを指定しない場合にSDKが使用するデフォルトの認証情報です。
  • [my-profile] のような名前付きプロファイルを作成できます。

このファイルを作成しておけば、SDKは特別な設定なしにデフォルトのプロファイル ([default]) から認証情報を読み込んでくれます。

4.3. 共有設定ファイル (~/.aws/config) の設定

同じくホームディレクトリの.awsサブディレクトリにconfigという名前のファイルを作成します。このファイルは、認証情報だけでなく、デフォルトリージョンや出力フォーマットなどの設定を保持できます。credentialsファイルと同様にプロファイルベースで設定できます。

“`ini
[default]
region = us-east-1
output = json

[my-profile]
region = ap-northeast-1
output = json
credential_source = Ec2InstanceMetadata # または Environment, EcsContainer

[profile cross-account-role]
region = ap-northeast-1
role_arn = arn:aws:iam::ACCOUNT_ID:role/RoleName
source_profile = my-profile # または credential_source = …
“`

  • region: デフォルトで使用するAWSリージョン。SDKクライアント作成時にリージョンを明示的に指定しない場合、この設定が使用されます。
  • credential_source: 認証情報の取得元を指定できます(例: Ec2InstanceMetadata, Environment, EcsContainer, SharedCredentialsFile)。
  • role_arn + source_profile/credential_source: 別のAWSアカウントのIAMロールを引き受ける (Assume Role) 設定です。source_profileまたはcredential_sourceで指定された認証情報を使って、role_arnで指定されたロールの認証情報を取得します。

SDKクライアントを作成する際に、特定のプロファイルを指定して認証情報を読み込むことができます。

“`java
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

// “my-profile”という名前のプロファイルから認証情報を読み込む
S3Client s3Client = S3Client.builder()
.region(Region.AP_NORTHEAST_1) // configファイルで指定しても良いが、コードで指定するのが一般的
.credentialsProvider(ProfileCredentialsProvider.create(“my-profile”))
.build();
“`

4.4. 環境変数を使用した認証

環境変数AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYを設定することでも認証情報を提供できます。一時的な認証情報を使用する場合は、AWS_SESSION_TOKENも設定します。

bash
export AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
export AWS_SESSION_TOKEN=YOUR_SESSION_TOKEN # 一時的な場合のみ
export AWS_REGION=us-east-1 # デフォルトリージョンも環境変数で設定可能

この方法はシンプルで、特にCI/CDパイプラインやコンテナ環境などでよく使用されますが、認証情報が環境変数としてプロセスに渡されるため、そのプロセスが実行される環境のセキュリティを確保することが重要です。

4.5. IAMロールを使用した認証(EC2, ECS, Lambdaなど)

AWSサービス上でアプリケーションを動作させる場合、IAMロールを使用するのが最も安全な方法です。アプリケーションを実行するEC2インスタンス、ECSタスク、またはLambda関数にIAMロールを割り当てます。このロールには、アプリケーションが必要とするAWSサービス操作の権限を付与します。

SDKは、EC2インスタンスメタデータサービス、ECSコンテナエージェント、またはLambda実行環境から自動的に一時的な認証情報(アクセスキー、シークレットキー、セッショントークン)を取得します。開発者はコード内で認証情報を明示的に指定する必要はありません。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

// IAMロールを使用する場合、通常認証情報プロバイダを明示的に指定する必要はない
// SDKは自動的に環境に応じたデフォルトのプロバイダチェーンを使用します
S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1) // リージョンは通常必要
// .credentialsProvider(…) // IAMロールの場合は不要(デフォルトが適切)
.build();
“`

これが最も推奨される方法であり、ハードコーディングや環境変数での認証情報管理に伴うリスクを排除できます。一時的な認証情報は定期的に自動更新されるため、キー漏洩のリスクも低減されます。

4.6. WebIdentityTokenを使用した認証(EKSサービスアカウントなど)

Kubernetes (EKS) 上のポッドで動作するアプリケーションがAWSサービスにアクセスする場合など、KubernetesサービスアカウントにIAMロールを紐付けるOIDC (OpenID Connect) による認証が利用されます。SDKはAWS_WEB_IDENTITY_TOKEN_FILEなどの環境変数から必要な情報を読み取り、STS (Security Token Service) を介して一時的な認証情報を取得します。この場合も、SDKはデフォルトの認証情報プロバイダチェーンの一部としてこれを自動的に検出します。

4.7. プログラムによる認証情報の設定

特別なケースでは、認証情報をプログラムで直接設定することも可能です。例えば、アクセスキーとシークレットキーをコード内で直接指定することもできます(非推奨ですが、テスト目的などで使用されることがあります)。

“`java
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(“YOUR_ACCESS_KEY_ID”, “YOUR_SECRET_ACCESS_KEY”))) // 非推奨!
.build();
“`

また、独自の認証情報プロバイダ実装を作成し、SDKに設定することも可能です。これは、認証情報をカスタムの秘密情報管理システムから取得する場合などに使用されます。

まとめると:

  • ローカル開発: 共有認証情報ファイル (~/.aws/credentials) または共有設定ファイル (~/.aws/config) の使用が最も便利です。
  • AWSサービス上 (EC2, ECS, Lambda): IAMロールの割り当てが最も安全で推奨される方法です。SDKが自動的に認証情報を取得します。
  • CI/CDや一時的な利用: 環境変数の使用も考えられますが、環境のセキュリティに注意が必要です。
  • ハードコーディングやシステムプロパティ: セキュリティリスクが高いため、極力避けてください。

SDKはこれらの方法を自動的に探しに行くため、通常は環境に応じて適切な認証情報を設定しておけば、コード側で特別な指定は不要です。

5. 主要AWSサービスとの連携:コードで体験する

ここでは、いくつかの主要なAWSサービスをJava SDK v2を使って操作する具体的なコード例を紹介します。同期クライアントを中心に説明しますが、非同期クライアントについても触れます。

コード例を実行する前に、前のセクションで説明した認証情報の設定と、該当サービスのSDKモジュールがプロジェクトの依存関係に追加されていることを確認してください。また、コード中で使用するバケット名、テーブル名、キュー名、関数名などは、ご自身のAWS環境に合わせて変更してください。

5.1. Amazon S3 (Simple Storage Service)

Amazon S3は、高い耐久性、可用性、スケーラビリティを持つオブジェクトストレージサービスです。ファイルやデータを「オブジェクト」として「バケット」と呼ばれるコンテナに保存します。

5.1.1. S3クライアントの作成

同期クライアントと非同期クライアントの両方を作成できます。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3AsyncClient;

// 同期クライアント
S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.build();

// 非同期クライアント
S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
.region(Region.US_EAST_1)
.build();
“`

クライアントは使い終わったら必ず.close()メソッドを呼び出すか、try-with-resources構文を使ってリソースを解放してください。

java
// try-with-resourcesを使った例
try (S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.build()) {
// S3操作を行うコード
} // s3Client.close() が自動的に呼び出される

5.1.2. バケットの一覧表示

AWSアカウントに存在するS3バケットの一覧を取得します。

“`java
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.Bucket;
import software.amazon.awssdk.services.s3.model.ListBucketsResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
import java.util.List;

try (S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1) // リージョンは任意 (ListBucketsはグローバルAPIだがクライアント構築には必要)
.build()) {

ListBucketsResponse response = s3Client.listBuckets();
List<Bucket> buckets = response.buckets();

System.out.println("My S3 buckets:");
if (buckets.isEmpty()) {
    System.out.println("  No buckets found.");
} else {
    for (Bucket bucket : buckets) {
        System.out.println("  " + bucket.name());
    }
}

} catch (S3Exception e) {
System.err.println(“Error listing buckets: ” + e.awsErrorDetails().errorMessage());
System.exit(1);
}
“`

listBuckets()メソッドはListBucketsResponseオブジェクトを返します。このオブジェクトからbuckets()メソッドでBucketオブジェクトのリストを取得できます。

5.1.3. オブジェクトのアップロード (PutObject)

指定したバケットにファイルをアップロードします。PutObjectRequestにはバケット名とオブジェクトキー(ファイルパスのようなもの)を指定します。アップロードするデータはRequestBodyオブジェクトで提供します。

“`java
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
import java.nio.file.Paths;

String bucketName = “my-unique-bucket-name-12345”; // 既存のバケット名に置き換える
String objectKey = “my-uploaded-file.txt”;
String filePath = “/path/to/your/local/file.txt”; // アップロードするローカルファイルのパス

try (S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.build()) {

PutObjectRequest putObjectRequest = PutObjectRequest.builder()
    .bucket(bucketName)
    .key(objectKey)
    .build();

// ファイルパスからRequestBodyを作成する場合
// RequestBody requestBody = RequestBody.fromFile(Paths.get(filePath));

// 文字列からRequestBodyを作成する場合
RequestBody requestBody = RequestBody.fromString("Hello, AWS S3!");

PutObjectResponse response = s3Client.putObject(putObjectRequest, requestBody);

System.out.println("Object uploaded successfully. ETag: " + response.eTag());

} catch (S3Exception e) {
System.err.println(“Error uploading object: ” + e.awsErrorDetails().errorMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
“`

RequestBody.fromFile()RequestBody.fromString()など、様々な方法でアップロードするデータを指定できます。大きなファイルを扱う場合は、RequestBody.fromFile()を使用するか、Multipart Upload APIを検討してください。

5.1.4. オブジェクトのダウンロード (GetObject)

指定したバケットのオブジェクトをダウンロードします。ダウンロードしたデータはResponseInputStreamとして取得できます。

“`java
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;

String bucketName = “my-unique-bucket-name-12345”; // 既存のバケット名に置き換える
String objectKey = “my-uploaded-file.txt”; // ダウンロードするオブジェクトキー
String downloadFilePath = “/path/to/save/downloaded-file.txt”; // 保存するローカルファイルのパス

try (S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.build()) {

GetObjectRequest getObjectRequest = GetObjectRequest.builder()
    .bucket(bucketName)
    .key(objectKey)
    .build();

try (ResponseInputStream<GetObjectResponse> s3Object = s3Client.getObject(getObjectRequest);
     OutputStream outputStream = Files.newOutputStream(Paths.get(downloadFilePath))) {

    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = s3Object.read(buffer)) != -1) {
        outputStream.write(buffer, 0, bytesRead);
    }

    System.out.println("Object downloaded successfully to " + downloadFilePath);

} catch (IOException e) {
    System.err.println("Error reading/writing file stream: " + e.getMessage());
}

} catch (S3Exception e) {
System.err.println(“Error downloading object: ” + e.awsErrorDetails().errorMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
“`

ResponseInputStreamはダウンロードされるデータをストリームとして提供します。大きなファイルをダウンロードする場合でも、メモリを圧迫することなく処理できます。ダウンロードが終わったらストリームをクローズするのを忘れないでください。

5.1.5. オブジェクトの削除 (DeleteObject)

指定したバケットのオブジェクトを削除します。

“`java
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;

String bucketName = “my-unique-bucket-name-12345”; // 既存のバケット名に置き換える
String objectKey = “my-uploaded-file.txt”; // 削除するオブジェクトキー

try (S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.build()) {

DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
    .bucket(bucketName)
    .key(objectKey)
    .build();

s3Client.deleteObject(deleteObjectRequest);

System.out.println("Object deleted successfully: " + objectKey);

} catch (S3Exception e) {
System.err.println(“Error deleting object: ” + e.awsErrorDetails().errorMessage());
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
“`

deleteObject()メソッドは成功した場合、レスポンスボディを持たないため、特に返り値のオブジェクトを確認する必要はありません。

5.1.6. 非同期S3操作

S3AsyncClientを使用すると、非同期でS3操作を実行できます。メソッドはCompletableFutureを返します。

“`java
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import java.util.concurrent.CompletableFuture;
import java.nio.file.Paths;

String bucketName = “my-unique-bucket-name-12345”; // 既存のバケット名に置き換える
String objectKey = “async-uploaded-file.txt”;
String filePath = “/path/to/your/local/file.txt”; // アップロードするローカルファイルのパス

try (S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
.region(Region.US_EAST_1)
.build()) {

PutObjectRequest putObjectRequest = PutObjectRequest.builder()
    .bucket(bucketName)
    .key(objectKey)
    .build();

// ファイルパスからAsyncRequestBodyを作成する場合
// AsyncRequestBody asyncRequestBody = AsyncRequestBody.fromFile(Paths.get(filePath));

// 文字列からAsyncRequestBodyを作成する場合
AsyncRequestBody asyncRequestBody = AsyncRequestBody.fromString("This is an async upload test.");


CompletableFuture<PutObjectResponse> future = s3AsyncClient.putObject(putObjectRequest, asyncRequestBody);

// 非同期処理の完了を待つ(Lambdaなどでは不要な場合が多い)
// future.join(); // 注意: ブロッキング呼び出し

System.out.println("Async upload initiated for " + objectKey);

// 非同期処理が完了した後のコールバックを設定することもできる
future.whenComplete((response, error) -> {
    if (error != null) {
        System.err.println("Async upload failed: " + error.getMessage());
    } else {
        System.out.println("Async upload completed successfully. ETag: " + response.eTag());
    }
});

// アプリケーションがすぐに終了しないように待機するなど
// Thread.sleep(5000); // 例
future.join(); // 簡単なテストコードでは待機が必要
System.out.println("Async operation finished.");

} catch (Exception e) {
e.printStackTrace();
}
“`

非同期処理は、多くのI/O待機が発生するシナリオ(例: 多数のファイルを並行してS3にアップロード/ダウンロードする)でアプリケーションのスループットを向上させるのに役立ちます。

5.1.7. エラーハンドリング

SDKはAWS API呼び出しでエラーが発生した場合、software.amazon.awssdk.core.exception.SdkExceptionまたはそのサブクラス(サービス固有の例外など)をスローします。AWSサービスから返されたエラー情報は、例外オブジェクトから取得できます。

“`java
try {
// S3操作コード
// s3Client.listBuckets();
throw S3Exception.builder().awsErrorDetails(
software.amazon.awssdk.services.s3.model.AwsErrorDetails.builder()
.errorMessage(“Simulated error: The specified bucket does not exist.”)
.errorCode(“NoSuchBucket”)
.serviceName(“S3”)
.build())
.statusCode(404)
.build();

} catch (S3Exception e) {
// S3固有の例外をキャッチ
System.err.println(“S3 error occurred:”);
System.err.println(” Status Code: ” + e.statusCode());
System.err.println(” Error Code: ” + e.awsErrorDetails().errorCode());
System.err.println(” Message: ” + e.awsErrorDetails().errorMessage());
System.err.println(” AWS Request ID: ” + e.requestId());
// さらに詳細な情報が必要であれば、e.awsErrorDetails() から取得
} catch (SdkException e) {
// SDK全般のエラーをキャッチ (ネットワーク問題、クライアント設定エラーなど)
System.err.println(“SDK error occurred: ” + e.getMessage());
} catch (Exception e) {
// その他の予期しないエラー
System.err.println(“An unexpected error occurred: ” + e.getMessage());
e.printStackTrace();
}
“`

サービス固有の例外クラス(例: S3Exception, DynamoDbExceptionなど)をキャッチすることで、より詳細なエラー情報を取得したり、エラーコードに基づいて特定の処理を行ったりできます。

5.2. Amazon DynamoDB

Amazon DynamoDBは、高速で柔軟なNoSQLデータベースサービスです。キーバリューおよびドキュメントデータモデルをサポートし、高いスケーラビリティとパフォーマンスを提供します。

5.2.1. DynamoDBクライアントの作成

同期クライアントと非同期クライアントがあります。NoSQLデータベースのデータ操作には、通常サービスクライアント (DynamoDbClient) を使用する方法と、POJOマッピングをサポートするEnhanced Client (DynamoDbEnhancedClient) を使用する方法があります。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient;

// 同期サービスクライアント
DynamoDbClient dynamoDbClient = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build();

// 非同期サービスクライアント
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbAsyncClient.builder()
.region(Region.US_EAST_1)
.build();

// 同期Enhanced Client (通常はこちらが便利)
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(dynamoDbClient) // サービスクライアントを紐付け
.build();

// 非同期Enhanced Client
DynamoDbEnhancedAsyncClient enhancedAsyncClient = DynamoDbEnhancedAsyncClient.builder()
.dynamoDbAsyncClient(dynamoDbAsyncClient)
.build();
“`

POJOマッピングを使用する場合は、Enhanced Clientが非常に便利です。この記事ではEnhanced Clientを中心に説明します。

5.2.2. DynamoDB Enhanced Client (POJOマッピング)

Enhanced Clientを使用するには、操作したいDynamoDBテーブルに対応するJava POJOクラスを作成し、アノテーションでマッピングを指定します。

前述のUserクラスの例を再掲します。

“`java
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; // 必要に応じて

@DynamoDbBean
public class User {
private String userId; // パーティションキー
private String email;
private String name;
// 他の属性…

@DynamoDbPartitionKey
public String getUserId() {
    return userId;
}

public void setUserId(String userId) {
    this.userId = userId;
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}
// 他のgetter/setter...

@Override
public String toString() {
    return "User{" +
           "userId='" + userId + '\'' +
           ", email='" + email + '\'' +
           ", name='" + name + '\'' +
           '}';
}

}
“`

@DynamoDbBeanアノテーションは、このクラスがDynamoDBアイテムにマッピングされることを示します。@DynamoDbPartitionKey@DynamoDbSortKey(複合キーの場合)は、それぞれパーティションキー属性とソートキー属性を示します。

POJOクラスを作成したら、Enhanced Clientからテーブルへの参照を取得します。

“`java
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

// クライアントとEnhanced Clientを作成 (try-with-resources推奨)
try (DynamoDbClient dynamoDbClient = DynamoDbClient.builder().region(Region.US_EAST_1).build();
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build()) {

// テーブルへの参照を取得
// "UserTable" は実際のDynamoDBテーブル名に置き換える
DynamoDbTable<User> userTable = enhancedClient.table("UserTable", TableSchema.fromBean(User.class));

// これ以降、userTable オブジェクトを使ってデータ操作を行う
// ...

}
“`

5.2.3. アイテムの書き込み (PutItem)

DynamoDbTable<User>オブジェクトを使って、Userオブジェクトをテーブルに書き込みます。

“`java
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;

try (DynamoDbClient dynamoDbClient = DynamoDbClient.builder().region(Region.US_EAST_1).build();
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build()) {

DynamoDbTable<User> userTable = enhancedClient.table("UserTable", TableSchema.fromBean(User.class));

// 書き込むUserオブジェクトを作成
User newUser = new User();
newUser.setUserId("user#123");
newUser.setName("Jane Doe");
newUser.setEmail("[email protected]");

// アイテムをテーブルに書き込み
userTable.putItem(newUser);

System.out.println("User item added to DynamoDB: " + newUser.getUserId());

} catch (DynamoDbException e) {
System.err.println(“Error writing item to DynamoDB: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

putItem()メソッドは、指定されたパーティションキー(およびソートキー)を持つアイテムが存在する場合、それを上書きします。

5.2.4. アイテムの読み込み (GetItem)

パーティションキー(およびソートキー)を指定して、単一のアイテムを読み込みます。

“`java
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.Key; // Keyクラスをインポート
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;

String userIdToGet = “user#123”; // 読み込むアイテムのパーティションキー

try (DynamoDbClient dynamoDbClient = DynamoDbClient.builder().region(Region.US_EAST_1).build();
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build()) {

DynamoDbTable<User> userTable = enhancedClient.table("UserTable", TableSchema.fromBean(User.class));

// 読み込むアイテムのKeyオブジェクトを作成
Key key = Key.builder()
    .partitionValue(userIdToGet)
    // .sortValue(sortKey) // 複合キーの場合はソートキーも指定
    .build();

// Keyを指定してアイテムを読み込み
User retrievedUser = userTable.getItem(key);

if (retrievedUser != null) {
    System.out.println("User item retrieved: " + retrievedUser.toString());
} else {
    System.out.println("User item not found with ID: " + userIdToGet);
}

} catch (DynamoDbException e) {
System.err.println(“Error getting item from DynamoDB: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

getItem()メソッドは、指定されたキーを持つアイテムが存在しない場合、nullを返します。

5.2.5. アイテムの更新 (UpdateItem)

既存のアイテムを更新します。updateItem()メソッドは、POJOで指定された非nullの属性で既存のアイテムを上書きします。

“`java
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;

String userIdToUpdate = “user#123”; // 更新するアイテムのパーティションキー

try (DynamoDbClient dynamoDbClient = DynamoDbClient.builder().region(Region.US_EAST_1).build();
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build()) {

DynamoDbTable<User> userTable = enhancedClient.table("UserTable", TableSchema.fromBean(User.class));

// 更新するUserオブジェクトを作成 (キーと更新したい属性を設定)
User userToUpdate = new User();
userToUpdate.setUserId(userIdToUpdate); // キーは必須
userToUpdate.setEmail("[email protected]"); // 更新したい属性

// アイテムを更新
User oldUser = userTable.updateItem(userToUpdate); // 更新前のアイテムを返す(設定による)

System.out.println("User item updated: " + userIdToUpdate);
if (oldUser != null) {
     System.out.println("Old item: " + oldUser.toString());
}

} catch (DynamoDbException e) {
System.err.println(“Error updating item in DynamoDB: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

Enhanced ClientのupdateItem()は、渡されたPOJOの非null属性で既存のアイテムを上書きします。特定の属性だけを条件付きで更新したい場合など、より複雑な更新はサービスクライアントのupdateItem()メソッドを使用するか、Enhanced ClientのupdateItem()メソッドのオプションを詳細に設定する必要があります。

5.2.6. アイテムの削除 (DeleteItem)

指定したパーティションキー(およびソートキー)を持つアイテムを削除します。

“`java
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;

String userIdToDelete = “user#123”; // 削除するアイテムのパーティションキー

try (DynamoDbClient dynamoDbClient = DynamoDbClient.builder().region(Region.US_EAST_1).build();
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build()) {

DynamoDbTable<User> userTable = enhancedClient.table("UserTable", TableSchema.fromBean(User.class));

// 削除するアイテムのKeyオブジェクトを作成
Key key = Key.builder()
    .partitionValue(userIdToDelete)
    // .sortValue(sortKey) // 複合キーの場合はソートキーも指定
    .build();

// Keyを指定してアイテムを削除
User deletedUser = userTable.deleteItem(key); // 削除されたアイテムを返す(設定による)

if (deletedUser != null) {
     System.out.println("User item deleted: " + deletedUser.toString());
} else {
    System.out.println("User item not found or already deleted with ID: " + userIdToDelete);
}

} catch (DynamoDbException e) {
System.err.println(“Error deleting item from DynamoDB: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

deleteItem()メソッドは、指定されたキーのアイテムが存在しない場合でもエラーにはなりません。

5.2.7. Scan操作とQuery操作

Scanはテーブル全体またはセカンダリインデックス全体をスキャンしてアイテムを取得します。Queryは特定のパーティションキーを指定し、必要であればソートキーで絞り込んでアイテムを取得します。QueryはScanより効率的です。Enhanced Clientでは、Pagerを使ってこれらの操作の結果を簡単に処理できます。

“`java
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; // Query用
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest; // Scan用
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; // Query用
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

try (DynamoDbClient dynamoDbClient = DynamoDbClient.builder().region(Region.US_EAST_1).build();
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build()) {

DynamoDbTable<User> userTable = enhancedClient.table("UserTable", TableSchema.fromBean(User.class));

// --- Scan操作の例 ---
System.out.println("Scanning all users:");
ScanEnhancedRequest scanRequest = ScanEnhancedRequest.builder().build(); // オプションなしのフルスキャン
userTable.scan(scanRequest).items().forEach(user -> System.out.println("  Scan found: " + user.toString()));

// --- Query操作の例 ---
// 特定のパーティションキー "user#123" をクエリ
System.out.println("\nQuerying user with ID user#123:");
QueryConditional queryConditional = QueryConditional.partitionValue("user#123");
QueryEnhancedRequest queryRequest = QueryEnhancedRequest.builder()
    .queryConditional(queryConditional)
    .build();

userTable.query(queryRequest).items().forEach(user -> System.out.println("  Query found: " + user.toString()));

// 複合キーとQuery操作の例
// GSI (Global Secondary Index) を指定したQueryも可能
// QueryConditional queryOnGsi = QueryConditional.partitionValue("value-for-gsi-pk");
// QueryEnhancedRequest queryRequestGsi = QueryEnhancedRequest.builder()
//     .queryConditional(queryOnGsi)
//     .indexName("YourGsiName")
//     .build();
// userTable.index("YourGsiName").query(queryRequestGsi).items().forEach(...);

} catch (DynamoDbException e) {
System.err.println(“Error scanning/querying DynamoDB: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

scan()query()メソッドはPageIterableを返します。items()メソッドを使うと、Iterableとして結果を取得でき、ページングをSDKが内部で処理してくれます。大量のデータを扱う場合は、メモリ使用量に注意が必要です。limit()filter()などのオプションも利用できます。

5.3. Amazon SQS (Simple Queue Service)

Amazon SQSは、フルマネージド型のメッセージキューサービスです。アプリケーション間でメッセージを送受信するために使用され、システムのスケーラビリティと信頼性を向上させます。

5.3.1. SQSクライアントの作成

同期クライアントと非同期クライアントがあります。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.SqsAsyncClient;

// 同期クライアント
SqsClient sqsClient = SqsClient.builder()
.region(Region.US_EAST_1)
.build();

// 非同期クライアント
SqsAsyncClient sqsAsyncClient = SqsAsyncClient.builder()
.region(Region.US_EAST_1)
.build();
“`

5.3.2. キューの作成と取得

メッセージを送受信するには、まずキューが必要です。既存のキューのURLを取得するか、新しいキューを作成します。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.CreateQueueRequest;
import software.amazon.awssdk.services.sqs.model.GetQueueUrlRequest;
import software.amazon.awssdk.services.sqs.model.CreateQueueResponse;
import software.amazon.awssdk.services.sqs.model.GetQueueUrlResponse;
import software.amazon.awssdk.services.sqs.model.QueueDoesNotExistException; // インポート
import software.amazon.awssdk.services.sqs.model.SqsException; // インポート

String queueName = “my-java-test-queue”;
String queueUrl = null;

try (SqsClient sqsClient = SqsClient.builder()
.region(Region.US_EAST_1)
.build()) {

try {
    // 既存のキューのURLを取得しようとする
    GetQueueUrlRequest getQueueUrlRequest = GetQueueUrlRequest.builder()
        .queueName(queueName)
        .build();
    GetQueueUrlResponse getQueueUrlResponse = sqsClient.getQueueUrl(getQueueUrlRequest);
    queueUrl = getQueueUrlResponse.queueUrl();
    System.out.println("Using existing queue: " + queueUrl);

} catch (QueueDoesNotExistException e) {
    // キューが存在しない場合は新しく作成
    System.out.println("Queue does not exist, creating it...");
    CreateQueueRequest createQueueRequest = CreateQueueRequest.builder()
        .queueName(queueName)
        .build();
    CreateQueueResponse createQueueResponse = sqsClient.createQueue(createQueueRequest);
    queueUrl = createQueueResponse.queueUrl();
    System.out.println("Created new queue: " + queueUrl);
}

// これ以降、queueUrlを使ってメッセージ操作を行う
// ...

} catch (SqsException e) {
System.err.println(“Error with SQS: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

キューのURLは、メッセージの送受信や削除を行う際に必要になります。

5.3.3. メッセージの送信 (SendMessage)

指定したキューにメッセージを送信します。

“`java
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
import software.amazon.awssdk.services.sqs.model.SqsException;

String queueUrl = “YOUR_QUEUE_URL”; // 前のステップで取得したキューのURLに置き換える
String messageBody = “Hello from Java SQS SDK!”;

try (SqsClient sqsClient = SqsClient.builder()
.region(Region.US_EAST_1)
.build()) {

SendMessageRequest sendMessageRequest = SendMessageRequest.builder()
    .queueUrl(queueUrl)
    .messageBody(messageBody)
    .delaySeconds(10) // 必要であればメッセージを遅延送信
    .build();

SendMessageResponse sendMessageResponse = sqsClient.sendMessage(sendMessageRequest);

System.out.println("Message sent to SQS. Message ID: " + sendMessageResponse.messageId());

} catch (SqsException e) {
System.err.println(“Error sending message to SQS: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

メッセージボディは文字列で指定します。最大サイズや、FIFOキューの場合はメッセージグループID/重複排除IDなどの考慮事項があります。

5.3.4. メッセージの受信 (ReceiveMessage)

キューからメッセージを受信します。receiveMessage()メソッドは、キューにメッセージがあるまで最大でWaitTimeSeconds(ロングポーリング)待機します。

“`java
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;
import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse;
import software.amazon.awssdk.services.sqs.model.Message; // インポート
import software.amazon.awssdk.services.sqs.model.SqsException;
import java.util.List;

String queueUrl = “YOUR_QUEUE_URL”; // メッセージを受信するキューのURLに置き換える

try (SqsClient sqsClient = SqsClient.builder()
.region(Region.US_EAST_1)
.build()) {

ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.builder()
    .queueUrl(queueUrl)
    .maxNumberOfMessages(10) // 最大10件まで取得
    .waitTimeSeconds(20) // ロングポーリング (最大20秒待機)
    .build();

ReceiveMessageResponse receiveMessageResponse = sqsClient.receiveMessage(receiveMessageRequest);
List<Message> messages = receiveMessageResponse.messages();

if (messages.isEmpty()) {
    System.out.println("No messages received from the queue.");
} else {
    System.out.println("Received messages:");
    for (Message message : messages) {
        System.out.println("  Message ID: " + message.messageId());
        System.out.println("  Body: " + message.body());
        System.out.println("  Receipt Handle: " + message.receiptHandle());
        // メッセージ処理ロジックをここに記述
        // ...

        // メッセージ処理が完了したら、必ず削除する
        // deleteMessage(sqsClient, queueUrl, message.receiptHandle()); // 後述の削除メソッドを呼び出す
    }
}

} catch (SqsException e) {
System.err.println(“Error receiving messages from SQS: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

メッセージを受信すると、キューからすぐに削除されるわけではありません。Visibility Timeoutの間、他のコンシューマーからは見えなくなります。メッセージの処理が正常に完了したら、必ずdeleteMessage()を呼び出してキューから完全に削除する必要があります。message.receiptHandle()は、メッセージを削除する際に必要となる一時的な識別子です。

5.3.5. メッセージの削除 (DeleteMessage)

メッセージの処理が完了したら、そのメッセージをキューから削除します。

“`java
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
import software.amazon.awssdk.services.sqs.model.SqsException;

// このメソッドは、受信ループの中で呼び出されることを想定
public static void deleteMessage(SqsClient sqsClient, String queueUrl, String receiptHandle) {
try {
DeleteMessageRequest deleteMessageRequest = DeleteMessageRequest.builder()
.queueUrl(queueUrl)
.receiptHandle(receiptHandle) // 受信したメッセージのReceipt Handleを使用
.build();

    sqsClient.deleteMessage(deleteMessageRequest);
    System.out.println("Message deleted successfully: " + receiptHandle);

} catch (SqsException e) {
    System.err.println("Error deleting message from SQS: " + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
    e.printStackTrace();
}

}
“`

5.4. AWS Lambda

AWS Lambdaは、サーバーレスコンピューティングサービスです。コードを実行するためにサーバーのプロビジョニングや管理は不要です。Lambda関数をJavaで記述し、それをSDKを使って他のアプリケーションから呼び出すことができます。

5.4.1. Lambdaクライアントの作成

同期クライアントと非同期クライアントがあります。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.LambdaAsyncClient;

// 同期クライアント
LambdaClient lambdaClient = LambdaClient.builder()
.region(Region.US_EAST_1)
.build();

// 非同期クライアント
LambdaAsyncClient lambdaAsyncClient = LambdaAsyncClient.builder()
.region(Region.US_EAST_1)
.build();
“`

5.4.2. Lambda関数の同期呼び出し (Invoke)

Lambda関数を同期的に呼び出し、実行結果(戻り値やエラー)を待ちます。

“`java
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.InvokeRequest;
import software.amazon.awssdk.services.lambda.model.InvokeResponse;
import software.amazon.awssdk.services.lambda.model.LogType; // インポート
import software.amazon.awssdk.services.lambda.model.LambdaException; // インポート
import java.nio.charset.StandardCharsets;

String functionName = “my-java-lambda-function”; // 呼び出すLambda関数名に置き換える
String payload = “{\”key1\”:\”value1\”, \”key2\”:\”value2\”}”; // 関数に渡すJSONペイロード

try (LambdaClient lambdaClient = LambdaClient.builder()
.region(Region.US_EAST_1)
.build()) {

InvokeRequest invokeRequest = InvokeRequest.builder()
    .functionName(functionName)
    .payload(SdkBytes.fromString(payload, StandardCharsets.UTF_8))
    .logType(LogType.TAIL) // 実行ログをレスポンスに含める (BASE64エンコードされる)
    .build();

InvokeResponse invokeResponse = lambdaClient.invoke(invokeRequest);

System.out.println("Lambda invocation status code: " + invokeResponse.statusCode());

// レスポンスボディ (関数の戻り値) を取得
if (invokeResponse.payload() != null) {
    String result = invokeResponse.payload().asUtf8String();
    System.out.println("Lambda response payload: " + result);
}

// 実行ログを取得 (LogType.TAILを指定した場合)
if (invokeResponse.logResult() != null) {
    String log = new String(java.util.Base64.getDecoder().decode(invokeResponse.logResult()));
    System.out.println("Lambda execution log:\n" + log);
}

// 関数実行中にエラーが発生した場合 (エラーレスポンスが返される)
if (invokeResponse.functionError() != null) {
    System.err.println("Lambda function execution error: " + invokeResponse.functionError());
    // エラー詳細は payload() に含まれる場合がある
    if (invokeResponse.payload() != null) {
         System.err.println("Error details: " + invokeResponse.payload().asUtf8String());
    }
}

} catch (LambdaException e) {
// API呼び出し自体のエラー (IAM権限不足など)
System.err.println(“Lambda API error: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

Lambdaの同期呼び出しは、API Gatewayからのリクエスト処理など、即時の応答が必要なシナリオに適しています。

5.4.3. Lambda関数の非同期呼び出し

Lambda関数を非同期的に呼び出します。呼び出し元は関数の完了を待ちません。

“`java
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.InvokeRequest;
import software.amazon.awssdk.services.lambda.model.InvokeResponse;
import software.amazon.awssdk.services.lambda.model.InvocationType; // インポート
import software.amazon.awssdk.services.lambda.model.LambdaException;
import java.nio.charset.StandardCharsets;

String functionName = “my-java-lambda-function”; // 呼び出すLambda関数名に置き換える
String payload = “{\”key1\”:\”value1\”, \”key2\”:\”value2\”}”; // 関数に渡すJSONペイロード

try (LambdaClient lambdaClient = LambdaClient.builder()
.region(Region.US_EAST_1)
.build()) {

InvokeRequest invokeRequest = InvokeRequest.builder()
    .functionName(functionName)
    .invocationType(InvocationType.EVENT) // 非同期呼び出しを指定
    .payload(SdkBytes.fromString(payload, StandardCharsets.UTF_8))
    .build();

InvokeResponse invokeResponse = lambdaClient.invoke(invokeRequest);

System.out.println("Lambda asynchronous invocation status code: " + invokeResponse.statusCode());
// 非同期呼び出しの場合、statusCodeが202 Acceptedであれば成功
// 関数の実行結果やエラーは InvokeResponse には含まれない

} catch (LambdaException e) {
System.err.println(“Lambda API error: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

非同期呼び出し(InvocationType.EVENT)は、関数の実行結果をすぐに必要としないシナリオ(例: イベント処理、バッチ処理のトリガー)に適しています。呼び出しが成功したかどうかは、ステータスコード202で判断し、関数の実行エラーはLambdaの非同期呼び出しエラー処理設定(DLQなど)で処理します。

5.5. Amazon EC2 (Elastic Compute Cloud)

Amazon EC2は、AWSクラウドでスケーラブルなコンピューティングキャパシティを提供するサービスです。SDKを使ってEC2インスタンスの起動、停止、状態取得などを行うことができます。

5.5.1. EC2クライアントの作成

同期クライアントと非同期クライアントがあります。EC2 APIは非常に多岐にわたるため、ここでは簡単なインスタンス一覧取得の例のみを示します。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;

// 同期クライアント
Ec2Client ec2Client = Ec2Client.builder()
.region(Region.US_EAST_1)
.build();

// 非同期クライアント
Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
.region(Region.US_EAST_1)
.build();
“`

5.5.2. インスタンスの一覧表示

アカウント内のEC2インスタンス情報を取得します。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest;
import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse;
import software.amazon.awssdk.services.ec2.model.Reservation; // インポート
import software.amazon.awssdk.services.ec2.model.Instance; // インポート
import software.amazon.awssdk.services.ec2.model.Ec2Exception; // インポート

try (Ec2Client ec2Client = Ec2Client.builder()
.region(Region.US_EAST_1)
.build()) {

DescribeInstancesRequest request = DescribeInstancesRequest.builder().build(); // 全インスタンス取得
// DescribeInstancesRequest request = DescribeInstancesRequest.builder().instanceIds("i-1234567890abcdef0").build(); // 特定インスタンス取得

DescribeInstancesResponse response = ec2Client.describeInstances(request);

System.out.println("EC2 Instances:");
if (response.reservations().isEmpty()) {
    System.out.println("  No instances found.");
} else {
    for (Reservation reservation : response.reservations()) {
        for (Instance instance : reservation.instances()) {
            System.out.println("  Instance ID: " + instance.instanceId());
            System.out.println("  State: " + instance.state().name());
            System.out.println("  Instance Type: " + instance.instanceType());
            System.out.println("  Public IP: " + instance.publicIpAddress());
            System.out.println("  Private IP: " + instance.privateIpAddress());
            // タグ情報なども取得可能
            // instance.tags().forEach(tag -> System.out.println("    Tag: " + tag.key() + "=" + tag.value()));
            System.out.println("  ---");
        }
    }
}

} catch (Ec2Exception e) {
System.err.println(“Error describing EC2 instances: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

describeInstances()メソッドは、複数のReservationオブジェクトを含むレスポンスを返します。各Reservationは1つ以上のInstanceオブジェクトを含みます。大量のインスタンスがある場合は、結果がページングされる可能性があるため、Pagerを使用することを検討してください。

5.6. その他サービスの簡単な紹介

AWS SDK for Java v2は、ここに挙げたサービス以外にも、AWSが提供する多くのサービスに対応するクライアントを提供しています。

  • RDS (Relational Database Service): RDSインスタンスの管理(起動、停止、スナップショット作成など)。
  • SNS (Simple Notification Service): メッセージのパブリッシュとサブスクリプション管理。
  • CloudWatch: メトリクスの取得、ログの出力、アラームの設定など。
  • IAM (Identity and Access Management): ユーザー、グループ、ロール、ポリシーの管理。
  • Secrets Manager / Systems Manager Parameter Store: 機密情報や設定情報の取得。
  • Step Functions: ステートマシンの実行開始、状態取得。
  • API Gateway: APIのデプロイ、ステージ管理など(通常、ランタイムでの利用より管理目的が多い)。

これらのサービスも、基本的に同様の手順(適切なモジュールの依存関係追加、クライアント作成、リクエストオブジェクト構築、API呼び出し)で利用できます。SDKのドキュメントには、各サービスのクライアントの使い方や利用可能なAPIの詳細が記載されています。

6. AWS SDK for Javaの高度な機能と設計パターン

基本的なSDKの使い方が理解できたところで、より高度な設定やパターンについて見ていきましょう。これらは、アプリケーションのパフォーマンス、信頼性、保守性を向上させるために役立ちます。

6.1. クライアントの設定詳細

SDKクライアントのビルダーは、様々な設定オプションを提供しています。

6.1.1. リージョンの設定

前述の通り、リージョンの設定は重要です。region()メソッドで明示的に指定するのが一般的ですが、以下のようにデフォルトの解決順序に依存することも可能です。

java
// デフォルトプロバイダチェーンによるリージョン解決に依存
S3Client s3Client = S3Client.builder().build();
// SDKは環境変数, configファイルなどの設定を探しに行く

明示的に指定する場合:

java
import software.amazon.awssdk.regions.Region;
// ...
S3Client s3Client = S3Client.builder()
.region(Region.AP_NORTHEAST_1) // 東京リージョン
.build();

Regionクラスは、AWSがサポートするすべてのリージョンの定数を提供しています。

6.1.2. HTTPクライアントの設定(Apache HttpClient, Netty, JDK HttpClient)

デフォルト以外のHTTPクライアントを使用したい場合、httpClientBuilder()メソッドを使用します。

例:Apache HttpClient (同期クライアントのデフォルト) を明示的に設定

java
import software.amazon.awssdk.http.apache.ApacheHttpClient;
// ...
S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.httpClientBuilder(ApacheHttpClient.builder()
// Apache HttpClient固有の設定をここで行う
.socketTimeout(Duration.ofSeconds(10))
.connectionTimeout(Duration.ofSeconds(10))
.maxConnections(100)
// その他、プロキシ設定など
)
.build();

例:Netty HttpClient (非同期クライアントのデフォルト) を明示的に設定

java
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
// ...
S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
.region(Region.US_EAST_1)
.httpClientBuilder(NettyNioAsyncHttpClient.builder()
// Netty HttpClient固有の設定をここで行う
.maxConnectionsPerEndpoint(50)
.connectionTimeout(Duration.ofSeconds(5))
// その他、イベントループグループの設定など
)
.build();

例:JDK HttpClient (Java 11以降) を使用

“`java
import software.amazon.awssdk.http.jdk.JdkHttpClient;
// …
S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.httpClient(JdkHttpClient.create()) // build()ではなくcreate()を使用
.build();

S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
.region(Region.US_EAST_1)
.httpClient(JdkHttpClient.create()) // build()ではなくcreate()を使用
.build();
“`

HTTPクライアントの設定は、接続タイムアウト、読み取りタイムアウト、接続プールサイズ、プロキシ設定など、ネットワーク通信に関する重要な側面を制御します。アプリケーションの負荷特性やネットワーク環境に合わせて適切に設定することで、パフォーマンスや安定性を改善できます。

6.1.3. タイムアウトと接続プールの設定

クライアントビルダーのoverrideConfiguration()メソッドを使って、リトライ戦略、タイムアウト、APIコールごとの設定などを一括で定義できます。

“`java
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.retry.RetryPolicy;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.regions.Region;
import java.time.Duration;

S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.overrideConfiguration(ClientOverrideConfiguration.builder()
.apiCallTimeout(Duration.ofSeconds(10)) // APIコール全体のタイムアウト
.apiCallAttemptTimeout(Duration.ofSeconds(5)) // 各APIコール試行のタイムアウト
// .retryPolicy(…) // 後述のリトライ設定
.build())
.build();
“`

HTTPクライアント側の接続タイムアウトやソケットタイムアウトと組み合わせて、ネットワーク関連のタイムアウト設定を行います。

6.1.4. プロキシ設定

HTTPクライアントビルダーやクライアントオーバーライド設定を使って、プロキシ設定が可能です。

java
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.utils.ProxyConfiguration;
// ...
S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.httpClientBuilder(ApacheHttpClient.builder()
.proxyConfiguration(ProxyConfiguration.builder()
.host("your.proxy.server")
.port(8080)
.scheme("http") // または "https"
// .username("user") // 認証が必要な場合
// .password("password") // 認証が必要な場合
// .nonProxyHosts("localhost|*.example.com") // プロキシを経由しないホスト
.build())
)
.build();

6.2. リトライ戦略

ネットワークの一時的な問題、スロットリング(レート制限)、サービス側の瞬間的なエラーなどが発生した場合、SDKは自動的にAPI呼び出しをリトライします。デフォルトのリトライ戦略は、一般的なケースに対応するように設定されていますが、必要に応じてカスタマイズできます。

6.2.1. デフォルトのリトライ戦略

SDKのデフォルトのリトライ戦略は、エラータイプに応じて指数バックオフやジッター(ランダムな遅延)を組み合わせた賢明なリトライを行います。通常はデフォルト設定で十分です。

6.2.2. カスタムリトライ戦略

ClientOverrideConfigurationでカスタムのRetryPolicyを設定できます。

“`java
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.retry.RetryPolicy;
import software.amazon.awssdk.core.retry.backoff.FullJitterBackoffStrategy;
import software.amazon.awssdk.core.retry.conditions.RetryCondition;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import java.time.Duration;

S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.overrideConfiguration(ClientOverrideConfiguration.builder()
.retryPolicy(RetryPolicy.builder()
.retryCondition(RetryCondition.defaultRetryCondition()) // デフォルトのリトライ条件
.backoffStrategy(FullJitterBackoffStrategy.builder() // 指数バックオフ + フルジッター
.baseDelay(Duration.ofMillis(100)) // 最初の遅延時間の基本値
.maxDelay(Duration.ofSeconds(5)) // 最大遅延時間
.build())
.maxAttempts(5) // 最大リトライ回数 (合計試行回数は maxAttempts + 1)
.build())
.build())
.build();
“`

リトライ回数、遅延戦略、リトライ対象となるエラー条件などを細かく制御できます。ただし、不用意なカスタム設定は予期しない挙動を引き起こす可能性があるため、デフォルト設定で問題ないかまず検討してください。

6.3. 非同期処理とCompletableFuture

非同期クライアントはメソッドの戻り値としてCompletableFutureを返します。これはJava 8で導入された非同期計算の結果を表すクラスで、後続処理をノンブロッキングでチェインしたり、複数の非同期処理の結果を組み合わせたりするのに非常に強力です。

“`java
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import java.util.concurrent.CompletableFuture;

// … S3AsyncClient の作成 …

PutObjectRequest request1 = PutObjectRequest.builder().bucket(“b1”).key(“f1”).build();
PutObjectRequest request2 = PutObjectRequest.builder().bucket(“b2”).key(“f2”).build();

// 2つの非同期アップロードを並行して実行
CompletableFuture future1 = s3AsyncClient.putObject(request1, AsyncRequestBody.fromString(“data1”));
CompletableFuture future2 = s3AsyncClient.putObject(request2, AsyncRequestBody.fromString(“data2”));

// 両方のタスクが完了したら実行される処理
CompletableFuture.allOf(future1, future2).whenComplete((result, error) -> {
if (error != null) {
System.err.println(“One or more async operations failed: ” + error.getMessage());
} else {
System.out.println(“All async operations completed successfully.”);
// 個別の結果を取得 (join()はブロッキング)
// PutObjectResponse res1 = future1.join();
// PutObjectResponse res2 = future2.join();
}
});

// アプリケーションの終了をブロックしないように注意が必要
// 例えば、メインスレッドを待機させる
// future1.join();
// future2.join();
“`

whenComplete(), thenAccept(), thenApply(), thenCompose(), exceptionally()などのメソッドを使って、非同期処理の結果に基づいてコールバック関数を定義できます。これにより、コールバック地獄に陥ることなく、複雑な非同期ワークフローを構築できます。

6.4. Pagerを用いたページネーション処理

AWS APIの中には、大量のリソース(例: S3バケット内のオブジェクト、EC2インスタンス、DynamoDBアイテム)を返すものがあります。これらのAPIは通常、結果をページ分割して返します。SDK v2はPagerインターフェースを提供しており、ページ分割された結果をIteratorやStreamのように簡単に処理できます。

例:S3バケット内のオブジェクト一覧をPagerを使って取得

“`java
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
import software.amazon.awssdk.services.s3.model.S3Object;
import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable; // Pager用のIterableをインポート
import software.amazon.awssdk.regions.Region;

String bucketName = “my-unique-bucket-name-12345”; // バケット名に置き換える

try (S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.build()) {

ListObjectsV2Request listObjectsRequest = ListObjectsV2Request.builder()
    .bucket(bucketName)
    // .prefix("my-folder/") // 特定のプレフィックスでフィルタリング
    // .maxKeys(100) // 1ページあたりの最大キー数 (デフォルトは1000)
    .build();

ListObjectsV2Iterable listObjectsPager = s3Client.listObjectsV2Paginator(listObjectsRequest);

System.out.println("Objects in bucket '" + bucketName + "':");
// Iteratorのようにページを順次処理
listObjectsPager.stream().flatMap(response -> response.contents().stream()) // 各ページのオブジェクトリストを平坦化
    .forEach(s3Object -> System.out.println("  " + s3Object.key() + " (Size: " + s3Object.size() + ")"));

} catch (Exception e) {
e.printStackTrace();
}
“`

listObjectsV2Paginator()メソッドは、ListObjectsV2Iterableを返します。このIterableは、内部で必要に応じて次ページのAPI呼び出しを行います。stream()メソッドを使ってJava Stream APIと連携させることも可能です。DynamoDBのscan()query()メソッドも、デフォルトでPagerを返します。

6.5. Waitersを用いたリソース状態の待機

AWSリソースの作成や変更操作は、非同期で行われることが多いです。例えば、EC2インスタンスの起動や、DynamoDBテーブルの作成は完了までに時間がかかります。このような場合、リソースの状態が特定の状態になるまでポーリングして待機する必要が出てきます。SDK v2はWaitersを提供しており、この待機処理を簡単に実装できます。

例:S3バケットが存在するまで待機

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.waiters.S3Waiter; // Waiterをインポート
import software.amazon.awssdk.services.s3.model.HeadBucketRequest; // チェックするAPIリクエストをインポート
import software.amazon.awssdk.core.waiters.WaiterResponse; // WaiterResponseをインポート
import software.amazon.awssdk.services.s3.model.S3Exception; // エラーをインポート

String bucketName = “my-unique-new-bucket-to-be-created”; // 待機するバケット名に置き換える

try (S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1)
.build();
S3Waiter waiter = s3Client.waiter()) { // S3クライアントからWaiterを取得

HeadBucketRequest headBucketRequest = HeadBucketRequest.builder()
    .bucket(bucketName)
    .build();

System.out.println("Waiting for bucket '" + bucketName + "' to exist...");

// バケットが存在するようになるまで待機
// 最大約10分、10秒間隔でポーリング
WaiterResponse<HeadBucketRequest> waiterResponse = waiter.waitUntilBucketExists(headBucketRequest);

// 待機結果のログ出力など
waiterResponse.matched().response().ifPresent(response ->
    System.out.println("Bucket exists. Matched response: " + response));
waiterResponse.matched().exception().ifPresent(exception ->
    System.err.println("Waiter failed waiting for bucket: " + exception.getMessage()));

System.out.println("Bucket '" + bucketName + "' is now available.");

} catch (S3Exception e) {
System.err.println(“Error waiting for bucket: ” + e.awsErrorDetails().errorMessage());
} catch (Exception e) {
e.printStackTrace();
}
“`

S3WaiterのようなWaiterクラスは、サービス固有のクライアントから取得できます。waitUntilXXX()のようなメソッドを使って、特定のリソース状態(例: waitUntilInstanceRunning, waitUntilTableExistsなど)になるまでポーリングを行います。待機処理はタイムアウトする可能性があるので、適切なエラーハンドリングが必要です。

6.6. カスタムエンドポイントとローカル開発

LocalStackのようなローカル環境でAWSサービスをエミュレートするツールを使用する場合、SDKがデフォルトのエンドポイント(例: s3.amazonaws.com)ではなく、ローカルのエミュレーターのエンドポイント(例: localhost:4566)にアクセスするように設定する必要があります。

これは、クライアントビルダーでendpointOverride()メソッドを使って行います。

“`java
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import java.net.URI;

// LocalStackなどでS3をエミュレートしている場合
S3Client s3Client = S3Client.builder()
.region(Region.US_EAST_1) // リージョンはダミーでも良いが指定が必要
.endpointOverride(URI.create(“http://localhost:4566”)) // LocalStackのS3エンドポイント
.build();

// LocalStack以外のエミュレーターや、特定のリージョンエンドポイントを強制したい場合も使用可能
“`

endpointOverride()を使用すると、認証情報の扱いなどがデフォルトと異なる場合があるため、エミュレーターのドキュメントをよく確認してください。LocalStackの場合は、ダミーのアクセスキーやリージョンを指定するだけで動作することが多いです。

6.7. ロギングの設定

SDK v2は内部でSlf4jを使用してロギングを行っています。アプリケーションでロギングフレームワーク(Logback, Log4j2など)を設定し、Slf4jの実装ライブラリを依存関係に追加することで、SDKの内部ログを確認できます。

例:Logbackを使用し、SDKのINFOレベルログを出力する場合 (logback.xml)

“`xml


%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} – %msg%n






“`

software.amazon.awssdkパッケージのロガーレベルを調整することで、SDKがAPIリクエスト/レスポンス、リトライ試行、認証情報解決などの情報をログに出力するようになります。デバッグ時に非常に役立ちます。

7. デバッグ、テスト、エラーハンドリング

AWS SDKを利用したアプリケーションの開発においては、デバッグ、テスト、そして適切なエラーハンドリングが不可欠です。

7.1. SDKのロギングを有効にする

前述のように、SDKの内部ロギングを有効にすることは、問題発生時の診断に非常に役立ちます。特にDEBUGレベルのログでは、送信されたHTTPリクエストの詳細、受信したレスポンス、認証情報の解決プロセス、リトライの状況などが確認できます。

開発環境やテスト環境では、software.amazon.awssdkパッケージのログレベルをDEBUGに設定することをお勧めします。本番環境では、ログの量に注意しながらINFOまたはWARNレベルに設定するのが一般的です。

7.2. エラーレスポンスの処理

AWS API呼び出しでエラーが発生した場合、SDKは例外をスローします。適切な例外処理を行うことで、アプリケーションの堅牢性を高めることができます。

7.2.1. AWS SDKのエラー階層

SDKのエラーは、software.amazon.awssdk.core.exception.SdkExceptionをルートとする階層構造になっています。

  • SdkException: SDKで発生した一般的なエラー(ネットワーク問題、設定エラーなど)。
  • AwsServiceException: AWSサービスがエラーレスポンスを返した場合。このクラスはさらにサービス固有のサブクラスを持ちます(例: S3Exception, DynamoDbException, LambdaExceptionなど)。
    • AwsServiceExceptionには、AWSから返されたエラーコード、エラーメッセージ、リクエストID、ステータスコードなどの情報が含まれます。
7.2.2. Specific Exception Handling

可能であれば、サービス固有の例外をキャッチして、エラーコードに基づいて処理を分岐させるのが最も効果的です。

“`java
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.model.NoSuchBucketException; // S3固有の例外をインポート
// …

try {
// S3操作
} catch (NoSuchBucketException e) {
// NoSuchBucketエラーの場合の特別な処理
System.err.println(“The specified bucket does not exist.”);
} catch (S3Exception e) {
// その他のS3エラーの場合
System.err.println(“Generic S3 Error: ” + e.awsErrorDetails().errorCode() + ” – ” + e.awsErrorDetails().errorMessage());
} catch (SdkException e) {
// その他のSDKエラー (ネットワークなど)
System.err.println(“SDK Error: ” + e.getMessage());
} catch (Exception e) {
// 予期しないその他のエラー
e.printStackTrace();
}
“`

特定のエラーコード(例: DynamoDBのConditionalCheckFailedException)に基づいて処理を変える必要がある場合も、サービス固有の例外をキャッチし、e.awsErrorDetails().errorCode()を確認します。

7.2.3. Generic Exception Handling

サービス固有のエラーコードすべてに対応するのが難しい場合や、一般的なエラー処理を行いたい場合は、AwsServiceExceptionSdkExceptionをキャッチします。これらの例外オブジェクトから、awsErrorDetails(), statusCode(), requestId()などのメソッドを使ってエラーの詳細を取得できます。requestId()は、AWSサポートに問い合わせる際に役立ちます。

7.3. LocalStackなどエミュレーターの活用

AWSクラウド環境に実際にアクセスすることなく、ローカルマシン上でAWSサービスとの連携部分を開発・テストするために、LocalStackのようなエミュレーターが非常に有用です。

LocalStackを起動し、SDKクライアントでendpointOverride()を設定することで、S3バケットの操作、SQSキューへのメッセージ送信、DynamoDBテーブルの操作などをローカルで実行できます。これにより、開発サイクルを高速化し、開発中のAWS利用料金を抑えることができます。

LocalStackのセットアップや使い方はここでは詳述しませんが、公式ドキュメントを参照して活用を検討してください。テストコードの中でエミュレーターをプログラムから起動・停止させるためのライブラリ(例: Testcontainers + LocalStackモジュール)もあります。

8. まとめ:クラウドネイティブJava開発への道

本記事では、JavaアプリケーションからAWSサービスを利用するための公式ライブラリであるAWS SDK for Java v2について、その概要、導入方法、主要サービスの操作例、そして高度な機能やエラーハンドリングまで、幅広く詳細に解説しました。

8.1. AWS SDK for Java v2 の価値

AWS SDK for Java v2は、Java開発者がAWSクラウドの持つポテンシャルを最大限に引き出すための強力なツールです。モジュール性、非同期処理の強化、使いやすいAPI設計など、v1からの大幅な進化により、より効率的で高性能なクラウドネイティブアプリケーション開発を支援します。

  • 開発効率の向上: AWS APIの詳細な仕様を意識することなく、Javaコードで直感的にサービスを操作できます。
  • パフォーマンス: 非同期クライアントや最適化されたHTTPクライアントにより、高いスループットや低いレイテンシを実現できます。
  • 保守性: モジュール構造やImmutableなオブジェクトにより、コードの見通しが良くなり、大規模開発でも管理しやすくなります。
  • セキュリティ: IAMロールとの連携など、安全な認証情報管理をサポートします。

これらの利点を活用することで、AWS上で動作するJavaアプリケーションを、より迅速に、より堅牢に構築できるようになります。

8.2. 次のステップ:さらに学ぶために

本記事は、AWS SDK for Java v2の基本的な使い方と主要機能を紹介するものでしたが、SDKは非常に多機能であり、AWSサービス自体も日々進化しています。さらに深く学ぶための次のステップとして、以下をお勧めします。

  • 特定のサービスを深く学ぶ: 自身のプロジェクトで主に使用するAWSサービス(S3、DynamoDB、SQSなど)に焦点を当て、そのサービスのSDKクライアントが提供するすべてのAPIや機能(例: S3のマルチパートアップロード、DynamoDBのトランザクションやストリーム、SQSのデッドレターキューなど)について、公式ドキュメントを読み込み、実際にコードを書いて試してみてください。
  • Spring Bootなどフレームワークとの連携: Spring Bootのようなフレームワークは、AWSサービスとの連携をさらに容易にするStarterモジュール(例: Spring Cloud AWS)を提供しています。これらを活用すると、設定が簡素化され、依存性注入などを利用した開発が効率的になります。
  • エラーハンドリングとリトライ戦略の詳細: アプリケーションの信頼性を高めるために、AWS SDKがスローする例外の種類を理解し、適切なリトライ戦略やデッドレターキュー(DLQ)などのアーキテクチャパターンを組み合わせて、エラー耐性のあるシステムを設計・実装してください。
  • 非同期プログラミングの習得: CompletableFutureを使った非同期処理のパターンを習得することは、AWS SDK v2の非同期クライアントの力を最大限に引き出すために重要です。

8.3. 参考リソース

AWS SDK for Java v2は、Java開発者がAWSクラウド上で革新的なアプリケーションを構築するための基盤となります。本記事が、皆様のクラウドジャーニーの一助となれば幸いです。

** Happy Coding! **

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール