Mac+Mavenで行うJava 25のAWS Lambda開発・デプロイして方法をまとめました


本記事は 夏休みクラウド自由研究2026 8/28付の記事です

皆さん、こんにちは!
夏に入ってからかき氷を食べれていなくてそろそろ秋になってしまいそう。いとさんです。

夏休みは自由研究よりも夏期講習!習い事!キャンプ!という怒涛の夏休みを過ごしておりました。
終わらない課題…迫りくる大会…目まぐるしい毎日が懐かしい…!

話を戻しまして、今回の夏休みクラウド自由研究ではJavaを使用したlambda関数の作成方法を調べました!結果をまとめて発表いたします。

なぜJavaのLambdaは難しいのか?

PythonやNode.js(JavaScript)であれば、AWSのコンソール画面に直接コードを貼り付けたり、.pyファイルを1枚アップロードするだけでLambdaが動きます。

しかし、Javaはそうはいきません。 いざ同じ感覚でコードをアップロードすると、以下のエラーが出力されます。

{
"errorMessage": "Class not found: com.example.lambda.Functions",
"errorType": "java.lang.ClassNotFoundException"
}

JavaをLambdaで動かすには、「Macローカルでのコンパイル(翻訳)」「外部ライブラリを巻き込んだパッケージング(Fat JAR化)」「AWS側のハンドラ設定」という工程を踏む必要があります。

この記事では、インフラ知識ゼロからでも迷わないよう、Macの環境構築からデプロイ成功までを詳細解説します。

ava 25 & Mavenの導入

Javaのソースコード(.java)は、人間が読むためのテキストです。これをコンピューターが理解できる.classファイルに変換(コンパイル)するために、MacにJDK(Java Development Kit)と、ビルド自動化ツールMaven(メイヴン)をインストールします。

1. Homebrewを使って一発インストール

Macのターミナル(zsh)を開き、以下のコマンドを1行ずつ実行します。

# 1. 最新のJava環境(OpenJDK 25)をインストール
brew install openjdk

# 2. Maven(ライブラリ管理・ビルドツール)をインストール
brew install maven

2. MacにJavaの居場所を教える(重要)

インストールしただけでは、MacのシステムがJavaを見つけられないことがあります(Unable to locate a Java Runtime というエラーの原因になります)。 そのため、以下のコマンドでパスを通します。

sudo ln -sfn $(brew --prefix)/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk

※sudoを実行すると、Macのログインパスワードを求められます。画面には何も表示されませんが、入力してEnterを押せば進みます。

 

3. 環境確認

正しく導入できたか、ターミナルでバージョンを確認します。

java -version
mvn -version

画面に openjdk version “25”、および Apache Maven … と表示されれば、Mac側の環境構築は完了です。

「フォルダ階層」の作成

Javaはフォルダの階層(パッケージ構造)に対して、全言語の中でもトップクラスに神経質です。 自分で手動作成する場合は、スペルミスに細心の注意を払ってください。特にsrc(ソース)をscrとタイポするミスが多発します。scrになるとMavenがコードを認識してくれません。

Mac上の任意の場所(例:ダウンロードフォルダ)に、以下の構造を完全に再現してください。

Plaintext

:file_folder: lambda-test (プロジェクトのルートフォルダ)
├── :page_facing_up: pom.xml <-- [位置注意] 常にこの最上層に置く
└── 📁 src
└── 📁 main
└── 📁 java
└── 📁 com
└── 📁 example
└── 📁 lambda <-- パッケージフォルダ
└── 📄 Functions.java

設計図(pom.xml)とコードの実装

1. pom.xml(Mavenの設計図)の作成

ルートフォルダ(lambda-test)の直下に pom.xml を作成します。 このファイルは、「Java 25でコンパイルすること」「AWS Lambdaのコア機能を読み込むこと」「SES(メール送信)のライブラリをネットからダウンロードしてくること」「それらを全部1つのJARファイルに合体(Shade)させること」が定義されています。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>lambda-experiment</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ses</artifactId>
<version>2.25.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

2. Functions.java の実装

一番奥の lambda フォルダ内に配置します。 Javaのルールとして、「1行目のpackage宣言」と「実際のフォルダ構造」が完全に一致していなければなりません。また、複数のハンドラを作る場合は、Javaが混乱しないように、それぞれ異なるメソッド名(handlerA, handlerB…)にする必要があります。

今回は、Lambda上で複数のハンドラ切り替え動作とSESでのメール送信を検証するための Functions.java の全コードです。

package com.example.lambda;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ses.SesClient;
import software.amazon.awssdk.services.ses.model.*;

import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.HashMap;

/**
* Java 25 AWS Lambda検証用クラス
* 
* 複数のハンドラメソッド(handlerA 〜 handlerT)を定義し、
* Lambdaコンソール側の「ハンドラ設定」を切り替えることで
* どのメソッドが実行されたかをSES経由で検証します。
*/
public class Functions implements RequestHandler<Map<String, String>, Map<String, Object>> {

// デフォルトハンドラ(RequestHandlerインターフェースの実装)
@Override
public Map<String, Object> handleRequest(Map<String, String> input, Context context) {
return handlerA(input, context);
}

// ─── ハンドラ定義 (A 〜 T の20個) ─────────────────────────────
// Lambdaコンソールのハンドラ設定例: com.example.lambda.Functions::handlerA
public Map<String, Object> handlerA(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerA", input, context); }
public Map<String, Object> handlerB(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerB", input, context); }
public Map<String, Object> handlerC(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerC", input, context); }
public Map<String, Object> handlerD(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerD", input, context); }
public Map<String, Object> handlerE(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerE", input, context); }
public Map<String, Object> handlerF(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerF", input, context); }
public Map<String, Object> handlerG(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerG", input, context); }
public Map<String, Object> handlerH(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerH", input, context); }
public Map<String, Object> handlerI(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerI", input, context); }
public Map<String, Object> handlerJ(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerJ", input, context); }
public Map<String, Object> handlerK(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerK", input, context); }
public Map<String, Object> handlerL(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerL", input, context); }
public Map<String, Object> handlerM(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerM", input, context); }
public Map<String, Object> handlerN(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerN", input, context); }
public Map<String, Object> handlerO(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerO", input, context); }
public Map<String, Object> handlerP(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerP", input, context); }
public Map<String, Object> handlerQ(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerQ", input, context); }
public Map<String, Object> handlerR(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerR", input, context); }
public Map<String, Object> handlerS(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerS", input, context); }
public Map<String, Object> handlerT(Map<String, String> input, Context context) { return sendEmailAndRespond("handlerT", input, context); }

// ─── 共通処理(SESメール送信 & レスポンス生成) ────────────────────
private Map<String, Object> sendEmailAndRespond(String handlerName,
Map<String, String> input,
Context context) {

// 環境変数から送信先・送信元メールアドレスを取得(未設定時のデフォルト値付き)
String recipient = System.getenv().getOrDefault("RECIPIENT_EMAIL", "example@example.com");
String sender = System.getenv().getOrDefault("SENDER_EMAIL", "example@example.com");
String region = System.getenv().getOrDefault("AWS_REGION", "us-east-1");

// 入力ペイロードからのパラメータ取得
String fileName = input != null ? input.getOrDefault("file_name", "unknown_file") : "unknown_file";
String folder = input != null ? input.getOrDefault("folder", "unknown_folder") : "unknown_folder";
String bucket = input != null ? input.getOrDefault("bucket", "unknown_bucket") : "unknown_bucket";

// 日本標準時(JST)の現在時刻を取得
String timeStr = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

String subject = "【" + handlerName + "】ファイル検知完了";

StringBuilder sb = new StringBuilder();
sb.append("Java 25 Lambda関数が正常に実行されました。\n\n");
sb.append("■ 実行ハンドラ情報\n");
sb.append("・呼び出された関数: ").append(handlerName).append("\n\n");
sb.append("■ 詳細情報\n");
sb.append("・ファイル名: ").append(fileName).append("\n");
sb.append("・フォルダ: ").append(folder).append("\n");
sb.append("・バケット: ").append(bucket).append("\n");
sb.append("・実行時刻: ").append(timeStr).append(" JST\n\n");
sb.append("このメールが正常に受信された場合、設定されたハンドラ「")
.append(handlerName)
.append("」がAWS Lambda上で正しく認識・実行された証明となります。");

try (SesClient ses = SesClient.builder().region(Region.of(region)).build()) {
// Amazon SES を使用したメール送信処理
ses.sendEmail(SendEmailRequest.builder()
.source(sender)
.destination(Destination.builder().toAddresses(recipient).build())
.message(Message.builder()
.subject(Content.builder().charset("UTF-8").data(subject).build())
.body(Body.builder()
.text(Content.builder().charset("UTF-8").data(sb.toString()).build())
.build())
.build())
.build());

Map<String, Object> result = new HashMap<>();
result.put("result", "Success");
result.put("executed_handler", handlerName);
result.put("timestamp", timeStr);
return result;

} catch (Exception e) {
context.getLogger().log("Error sending email in " + handlerName + ": " + e.getMessage());

Map<String, Object> errorResult = new HashMap<>();
errorResult.put("result", "Error");
errorResult.put("executed_handler", handlerName);
errorResult.put("message", e.getMessage());
return errorResult;
}
}
}

ターミナル操作によるコンパイルと検証

準備ができたら、ターミナルからMavenを動かしてコンパイル(ビルド)を行います。

1. 【超重要】「実行する場所」の罠

奥の lambda フォルダに移動してコマンドを叩くと、設計図(pom.xml)が見つからずにエラーになります。必ず一番外側のルートフォルダで実行します。

# pom.xmlが置いてあるフォルダに移動
cd /Users/任意のユーザー名/Downloads/lambda-test

# 目の前にpom.xmlがあるか確認
ls
# 出力結果に pom.xml と src/ が並んでいれば正解!

2. クリーンビルドの実行

満を持して、以下のコマンドを実行します。

mvn clean package

コマンドを実行すると、Mavenがネットから自動的にSESなどの必要なライブラリ(JAR)をダウンロードし、Javaコードのコンパイルを始めます。 画面の最後に緑色の文字で [INFO] BUILD SUCCESS と表示されれば大成功です!

3. JARファイルの中身の確認
本当にプログラムがJARの中に書き込まれたか、以下のコマンドで中身を検証できます。

jar tf target/lambda-experiment-1.0-SNAPSHOT.jar | grep Functions

成功時の出力:

Plaintext

com/example/lambda/Functions.class

これが表示されれば、中身が詰まった本物のJARファイルが完成した証拠です。

AWS Lambdaへのデプロイと最終設定

1. 「生のJAR」のままアップロード

AWS Lambdaコンソールを開き、対象の関数を選択します。

「コード」タブの「コードをアップロード元」から 「.zip または .jar ファイル」 を選択します。

target/ フォルダの中に生成された lambda-experiment-1.0-SNAPSHOT.jar を指定します。

:warning: 注意: このJARファイルをさらに右クリックなどでZIP圧縮してはいけません。 .jar ファイルのまま直接アップロードするのがJavaのルールです。

2. ランタイム(ハンドラ)設定の修正

LambdaがJARファイルを開いたとき、最初にどのクラスのどのメソッドを実行すればいいかを教えてあげる必要があります。ここがズレると「No public method named…」というエラーになります。

「ランタイム設定」の「編集」ボタンを押します。

ハンドラ欄に以下を入力します。

com.example.lambda.Functions::handlerA (「パッケージ名.クラス名::メソッド名」の構造です。コロンは2つ必要です)

エラーに遭遇したときのチェックリスト

もし動かなかった場合は、以下の3大原因を上からチェックしてください。

  • フォルダ名が scr になっていないか?(正しくは src)
  • 生成されたJARをさらにZIPに固めてアップロードしていないか?(.jar のままアップロードが正解)
  • AWS側のハンドラ設定の末尾が、コードに実在するメソッド名(::handlerA など)になっているか?

JavaのLambdaは最初の環境構築さえ超えれば、非常に強固で高速なシステムを組むことができます。この情報が、同じエラーで悩む誰かの助けになれば幸いです!

×
タイトルとURLをコピーしました