JavaプロジェクトのCIをMavenとGitHub Actionsで構築する
1. 概要
Continuous Integration (CI) は、コードをマージする時点でトラブルが発生するのを待つのではなく、プッシュ直後にバグを検出するのに役立ちます。このガイドでは、GitHub Actions で Java プロジェクト向けの CI パイプラインを構築する方法を説明します:ビルド、テスト実行、依存性のキャッシング、複数 JDK バージョンのビルドマトリックス、レポート公開。読み終わったら、ワークフローファイルを実際のプロジェクトにコピーして、数分で実行できます。
2. サンプルプロジェクトの準備
Maven の標準的な構造を持つシンプルな Spring Boot プロジェクトがあると仮定します。最小限の pom.xml ファイルは以下の通りです:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nonladev</groupId>
<artifactId>order-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<!-- コンパイラの Java バージョンを指定 -->
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- JUnit 5 フレームワーク -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<!-- アサーション用ライブラリ -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- テスト実行用プラグイン -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>
junit-jupiter と assertj-core 依存性はテストを書くために使用されます。maven-surefire-plugin は mvn test を実行した時にテストを実行する責任があります。
テスト対象となるシンプルなビジネスクラス:
package com.nonladev.order;
public class OrderCalculator {
public double calculateTotal(double unitPrice, int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantityは0より大きい必要があります");
}
return unitPrice * quantity;
}
}
対応するテスト:
package com.nonladev.order;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class OrderCalculatorTest {
private final OrderCalculator calculator = new OrderCalculator();
@Test
void shouldCalculateTotalCorrectly() {
double total = calculator.calculateTotal(50_000, 3);
assertThat(total).isEqualTo(150_000);
}
@Test
void shouldThrowWhenQuantityIsInvalid() {
assertThatThrownBy(() -> calculator.calculateTotal(50_000, 0))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Quantityは0より大きい必要があります");
}
}
shouldCalculateTotalCorrectly は合計が正しく計算されることを確認します。shouldThrowWhenQuantityIsInvalid は quantity が無効な場合に例外がスローされることを確認します。これは CI が毎回コードをプッシュするたびに実行するテストです。
3. 基本的な CI ワークフロー
GitHub Actions は .github/workflows/ ディレクトリ内の YAML ファイルを読みます。.github/workflows/ci.yml ファイルを作成します:
name: CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build and run tests
run: mvn -B clean verify
各部分の説明:
on:ワークフローはmainブランチへのpushまたはpull_request時に実行されます。actions/checkout@v4:ソースコードを runner にクローンします。actions/setup-java@v4:Temurin JDK 21 をインストールします。mvnを呼び出す前にこのステップが常に必要です。mvn -B clean verify:フラグ-B(バッチモード)はインタラクティブ出力を無効にしており、CI 環境に適しています。clean verifyは最初からビルドし、テストを実行します(verifyフェーズはtestを含みます)。
コミットをプッシュすると、GitHub の Actions タブで、次のようなログを含むジョブが実行されているのを見ることができます:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.nonladev.order.OrderCalculatorTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
テストが失敗した場合、ジョブは赤いステータスを持ち、ログにスタックトレースが表示されます。マシンに SSH でアクセスする必要はありません。
4. Maven の依存性キャッシングでビルドを高速化
ジョブが新しい runner で実行されるたびに(一時的な環境のため)、Maven は .m2/repository から依存性全体を再ダウンロードする必要があります。Spring Boot プロジェクトの場合、これには毎回 1~2 分かかることがあります。setup-java はキャッシングをサポートしています:
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
cache: 'maven' 属性は pom.xml ファイルのハッシュに基づいて ~/.m2/repository ディレクトリを自動的にキャッシュします。次回のビルドで pom.xml が変わらなければ、Actions はキャッシュをすぐに復元し、jar の再ダウンロードをスキップします。
キャッシュ追加前後の「ビルドとテスト実行」ステップの実行時間を確認することで効果を検証できます。通常、30~60% の削減が期待できます。
注意: プロジェクトが Maven Wrapper(
./mvnw)を使用している場合、runner にインストールされているデフォルトの Maven バージョンに依存する代わりに、mvnwに基づいてキャッシュすべきです。GitHub がイメージを更新するたびに、デフォルトの Maven バージョンが変わる可能性があるためです。
5. 複数の JDK バージョンでのビルドマトリックス
複数の Java バージョンとの互換性が必要なライブラリを保守している場合、strategy.matrix を使用して複数のジョブを並行実行します:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [ '17', '21' ]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: 'maven'
- name: Build and run tests
run: mvn -B clean verify
matrix.java-version はテスト対象のバージョンのリストです。GitHub Actions は自動的に 2 つのジョブを並行実行します:1 つは JDK 17 用、1 つは JDK 21 用です。変数 ${{ matrix.java-version }} は各ジョブで対応する値に置き換えられます。
Actions タブの結果には 2 つのジョブが別々に表示されます(例:build (17) と build (21))。これにより、プロジェクトがどの Java バージョンで失敗するかをすぐに確認できます。
6. テストレポートの公開とカバレッジの測定
デフォルトでは、テストログは Actions にテキスト形式でのみ表示されます。より読みやすいレポートを得るには、dorny/test-reporter アクションを使用します:
- name: Build and run tests
run: mvn -B clean verify
- name: Publish test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Maven Tests
path: '**/surefire-reports/*.xml'
reporter: java-junit
if: success() || failure():このステップはテストが失敗した場合でも実行されます(ジョブがキャンセルされていない限り)。常にレポートを表示できるようにします。path は Surefire がテスト実行後に生成する XML ファイルを指します。レポートは PR の個別チェックとして表示され、各テストケースの pass/fail をリストアップします。
カバレッジを測定したい場合は、pom.xml に JaCoCo プラグインを追加します:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
mvn verify を実行すると、自動的に target/site/jacoco/index.html に HTML レポートが生成されます。それをアーティファクトとしてアップロードできます:
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: jacoco-report
path: target/site/jacoco/
このアーティファクトはワークフロー実行のサマリーページからダウンロードでき、ブラウザで直接表示できます。
7. ビルドステータスバッジ
ワークフローが安定して実行されたら、README.md にバッジを追加して、Actions にアクセスしなくても誰もが CI ステータスを確認できるようにします:

<org>/<repo> を実際の GitHub リポジトリ名に置き換えます。バッジは最後の実行結果に応じて自動的に緑/赤で更新されます。
8. よくあるエラー
maven.compiler.sourceと一致するjava-versionを設定し忘れ:ローカルビルドは成功するが CI 失敗。pom.xmlとワークフローの JDK バージョンを常に同期してください。mvn verifyの代わりにmvn testを使用:プロジェクトに integration test があり、verifyフェーズで実行される場合(例えば Failsafe プラグイン)、mvn testはそれらをスキップし、誤った安心感を招きます。- 依存性を変更した時にキャッシュキーが間違っている:
actions/cacheを手動で記述していて、pom.xmlをキーハッシュに含めていない場合、古いキャッシュが廃止された依存性を使わせるかもしれません。setup-javaのcache: 'maven'を使用してこれを回避してください。 - Docker/ネットワークが安定していない self-hosted runner で CI 実行:Maven は中央リポジトリから依存性をダウンロードする必要があります。runner がネットワークブロック中の場合、ビルドはダウンロード段階で停止します。プロキシを確認するか、内部 Maven ミラーを使用してください。
注意: プロジェクトが非常に小さく、単純なテストのみで頻繁にデプロイしない場合、マトリックス + カバレッジレポートを含む完全な CI ワークフローは過度かもしれません。セクション 3 の基本的なワークフローから始めて、プロジェクトが成長するにつれて段階的に拡張してください。
9. まとめ
数十行の YAML で、Java プロジェクトの CI パイプラインを自動的にビルド、テスト、依存性をキャッシング、レポートを公開できます。シンプルなワークフローから始めて、必要に応じてマトリックス JDK、JaCoCo カバレッジ、README バッジを追加します。最も重要なのは複雑なワークフローではなく、CI が安定して信頼性のあるものであることです。