Article

⚡ GitHub ActionsのMavenビルドを速くする:setup-javaのcacheを正しく使う

githubactions, java, maven, ci

JavaプロジェクトのCIで、毎回Mavenの依存ライブラリをダウンロードしていませんか。

GitHub-hosted runnerはジョブごとに基本的に新しい環境から始まるため、何もしなければMavenが依存関係を再取得する時間が発生します。GitHub Actionsでは actions/setup-javacache: maven を使うだけで、Mavenのローカルリポジトリをキャッシュできます。

この記事では、単に設定例を載せるだけでなく、何がキャッシュされるのか、いつキャッシュが無効になるのか、マルチモジュールではどうするのか、キャッシュを過信してはいけない理由まで整理します。

まず結論

一般的なJava 17 + Mavenプロジェクトなら、まず次の形から始めれば十分です。

name: Java CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '17'
          cache: maven

      - name: Verify
        run: mvn --batch-mode verify

ポイントはここです。

cache: maven

GitHub公式ドキュメントでは、Mavenの依存関係キャッシュには setup-java を利用できると案内されています。キャッシュされたローカルMavenリポジトリは、後続のworkflow runで復元されます。

そもそもMavenのローカルリポジトリとは

Mavenは pom.xml に書かれた依存ライブラリをMaven Centralなどから取得します。

例えば次の依存関係があるとします。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

取得したJARなどは通常、ユーザーのホームディレクトリ配下の .m2/repository に保存されます。

ローカルPCでは、一度取得した依存関係を次回から再利用できます。しかしGitHub-hosted runnerはCI実行ごとに環境が作られるため、キャッシュを設定しなければ同じ依存関係を何度もネットワークから取得することになります。

そこでGitHub Actionsのdependency cacheを使います。

cache: mavenで何が起きるのか

actions/setup-java

with:
  cache: maven

を指定すると、Mavenの依存関係キャッシュを保存・復元する処理をsetup-java側に任せられます。

GitHubのJava + Maven公式ガイドでは、キャッシュキーは pom.xml の内容をもとに生成され、pom.xml が変わればキャッシュも更新されると説明されています。

つまりイメージとしては次の流れです。

1回目
pom.xml
  ↓
キャッシュなし
  ↓
Maven Centralなどから依存関係を取得
  ↓
.m2 の依存関係をキャッシュ

2回目
pom.xml
  ↓
対応するキャッシュあり
  ↓
キャッシュを復元
  ↓
mvn verify

依存関係に変更がなければ、毎回ゼロから取得する必要がなくなります。

actions/cacheを自分で書く必要はある?

単純なMavenプロジェクトなら、最初から actions/cache を手書きする必要はほとんどありません。

GitHub公式も、MavenやGradleでは各 setup-* actionを使うことで最小限の設定でdependency cacheを利用できると案内しています。

そのため、まずは

- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '17'
    cache: maven

で始めるのがおすすめです。

キャッシュ対象やキーを細かく制御したい場合に、初めて actions/cache を検討すると分かりやすいです。

マルチモジュールではcache-dependency-pathを検討する

実務では次のような構成もあります。

repository/
├── pom.xml
├── api/
│   └── pom.xml
└── batch/
    └── pom.xml

複数のPOMをキャッシュキーの計算対象として明示したい場合は、cache-dependency-path を利用できます。

- name: Set up JDK 17
  uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '17'
    cache: maven
    cache-dependency-path: |
      pom.xml
      api/pom.xml
      batch/pom.xml

特に独立したモジュール構成や、ルート以外のPOM変更を確実にキャッシュキーへ反映したいケースで確認したい設定です。

mvn dependency:go-offline を毎回入れれば速くなる?

CI高速化の記事で、次のような処理を見ることがあります。

mvn dependency:go-offline
mvn verify

dependency:go-offline は、プロジェクトの依存関係やプラグインなどを事前解決するMaven Dependency Pluginのゴールです。

ただし、通常のCIで毎回これを追加すれば必ず速くなるわけではありません。

mvn verify 自体も必要な依存関係を取得するため、単純なworkflowなら

cache: maven

mvn --batch-mode verify

から始め、実際の実行時間を計測してから追加の最適化を考える方が安全です。

CI高速化は「コマンドを増やすこと」ではなく、どこに時間がかかっているかを測って減らすことが重要です。

SNAPSHOT依存関係には注意

例えば社内ライブラリを

<version>1.2.0-SNAPSHOT</version>

のように参照している場合、キャッシュがあるからといってリモート側の最新SNAPSHOTを永久に確認しなくてよい、という意味ではありません。

Mavenの更新確認ポリシーや -U オプションなども関係します。

mvn -U verify

-U は、Mavenに対して不足しているreleaseや更新されたsnapshotを確認するよう指示するオプションです。

ただし毎回 -U を付ければネットワークアクセスが増える可能性があります。固定バージョン中心のプロジェクトとSNAPSHOTを多用するプロジェクトでは、CIの設計を分けて考えた方がよいでしょう。

キャッシュはビルド成果物ではない

ここは混同しやすいポイントです。

GitHub Actionsでは cacheartifact は用途が違います。

キャッシュは、依存関係など「次のCIでも再利用して処理を速くしたいファイル」のための仕組みです。

一方artifactは、JARやテスト結果など「workflowで生成した成果物を保存・参照したい」ときに使います。

例えばJARを保存したいなら、Maven cacheではなく upload-artifact を使います。

- name: Build
  run: mvn --batch-mode package

- name: Upload jar
  uses: actions/upload-artifact@v4
  with:
    name: application
    path: target/*.jar

.m2 のキャッシュとJARのartifactは別物、と覚えておくと混乱しません。

キャッシュがあってもCIは成功できる設計にする

キャッシュは高速化のための仕組みであり、ビルド成立の前提にしてはいけません。

GitHub公式ドキュメントでも、キャッシュが利用できなくても必要なファイルを再ダウンロードまたは再生成できるようにする考え方が示されています。

つまり、次の状態が理想です。

cache hit
  → 速く成功

cache miss
  → 少し遅いが成功

逆に

cache miss
  → ビルド失敗

になるなら、CIがキャッシュへ依存しすぎています。

セキュリティ面でもキャッシュを信用しすぎない

キャッシュにはsecretや認証情報を保存しないようにします。

GitHubは、復元したキャッシュを信頼できない入力として扱うことや、機密情報をキャッシュへ保存しないことを案内しています。

例えば以下のようなものを独自キャッシュへ含める設計は避けます。

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
GitHub PAT
秘密鍵
認証済み設定ファイル

依存ライブラリを高速に再利用する仕組みと、secret管理は分離します。

実務でまず使う構成

Java 17 + Mavenなら、私はまずこの程度から始めます。

name: CI

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  verify:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '17'
          cache: maven

      - name: Verify
        run: mvn --batch-mode verify

ここからActionsの実行時間を見て、必要ならテスト分割、Docker build cache、並列化など次のボトルネックを探します。

まとめ

GitHub ActionsでMaven CIを高速化するとき、まず覚えておきたいのは次の点です。

  • actions/setup-javacache: maven でMaven依存関係をキャッシュできる
  • キャッシュにより毎回の依存関係ダウンロードを減らせる
  • pom.xml の変更はキャッシュ更新に関係する
  • 複数POMを扱う場合は cache-dependency-path を検討する
  • dependency:go-offline を機械的に追加するのではなく、まず計測する
  • cacheは依存関係などの再利用用、artifactはJARなどの成果物保存用
  • cache missでもCIが成功できる設計にする
  • secretをキャッシュへ入れない

数行の設定ですが、JavaプロジェクトではCI時間を改善できる可能性があります。まず cache: maven を有効にし、変更前後のworkflow実行時間を比較するところから始めるのがおすすめです。

参考