Maven/Gradleで「隠れた依存関係」を暴く!未使用ライブラリによるプロジェクト肥大化を撃退する
テックリードの皆さん、日々のビルド待ち時間や、肥大化し続けるFat JAR/WARのサイズに絶望したことはないだろうか。
「誰も使っていないはずの古いライブラリがなぜかクラスパスに乗っている」
「推移的依存関係(Transitive Dependencies)のせいで、意図しない巨大なライブラリ群がサイレントに取り込まれている」
Javaエコシステム(Maven/Gradle)において、依存関係の管理は諸刃の剣だ。ビルドツールは賢いため、1つの依存関係を書くだけで関連するライブラリを自動で芋づる式に持ってくる。しかし、この「親切心」こそが、プロジェクトの腐敗とビルドパフォーマンス低下の元凶である。
本稿では、コードベースの裏側で静かにプロジェクトを蝕む「隠れた依存関係(Unused / Undeclared Dependencies)」を徹底的に暴き出し、ビルド時間短縮と配布サイズ縮小を達成するためのプロフェッショナルな実践手法を解説する。
—
1. なぜ「隠れた依存関係」がプロジェクトを殺すのか?
ビルドツールの内部では、リモートリポジトリから取得したJARファイルのメタデータ(POMやGradleのメタデータ)を解析し、クラスパス(Class-path)のツリーを構築している。ここで発生するのが以下の2つのアンチパターンだ。
1. Unused Dependencies(未使用の依存関係): かつて使っていたがコードのリファクタリングで不要になった、あるいはコピペしたpom.xml/build.gradleに最初から含まれていたもの。
2. Transitive Dependenciesの暴走: 直接依存(Direct Dependency)が要求している孫・ひ孫ライブラリが、アプリケーションの実行時やコンパイル時に悪影響を及ぼすもの。
これらが放置されると、以下の「実務上の致命傷」に直結する。
- コンパイル・インクリメンタルビルドの劣化: クラスパス上のJARが増えるほど、Javaコンパイラ(javac)やIDEのインデクサがスキャンすべきファイルサイズが増大し、CPUとI/Oを浪費する。
- セキュリティリスクの増大(脆弱性): 使っていないライブラリの古いバージョンにCVE(脆弱性)が存在し、SCA(Software Composition Analysis)ツールで無限のアラートに悩まされる。
- バイナリサイズ(Fat JAR)の肥大化: コンテナイメージ(Docker)のビルド・プッシュ・プル時間が無駄に延びる。
—
2. 隠れた依存関係を暴く武器:診断コマンドの極意
まずは、現状のガン細胞を特定する。MavenとGradle、それぞれの標準・拡張機能を用いた「可視化の極意」を授ける。
Mavenの場合: `dependency:analyze`
Mavenには、バイトコードとクラスパスを静的解析し、未使用の依存関係を暴く公式プラグインが標準で組み込まれている。
依存関係の静的解析を実行
mvn dependency:analyze
実行すると、以下のような診断レポートが標準出力に吐き出される。
[INFO] — maven-dependency-plugin:3.6.1:analyze (default-cli) @ core-service —
[WARNING] Used undeclared dependencies found:
[WARNING] org.springframework:spring-core:jar:6.1.2:compile
[WARNING] Unused declared dependencies found:
[WARNING] commons-io:commons-io:jar:2.11.0:compile
- `Used undeclared dependencies`: コード内で使っているが、pom.xmlに直接宣言していないもの(推移的依存にぶら下がっているため偶然ビルドできているが、非常に危険。依存関係のバージョンが変わった瞬間にビルドが壊れる)。
- `Unused declared dependencies`: pom.xmlに宣言しているが、バイトコード上では一度も参照されていないもの(即座に削除すべき無駄な荷物)。
Gradleの場合: Dependency Insight と Task活用
Gradleには `dependencyInsight` という、特定の依存関係がどこから引っ張られてきたかを追跡する強力な機能がある。さらに、未使用依存関係の検出には `jvm-ecosystem-analysis` や `Netflix/gradle-dependency-analyzer` などのプラグインを使用するのが定石だ。
特定のライブラリがどの依存関係ツリーから混入したかを完全に追跡する
./gradlew :app:dependencyInsight –dependency commons-io
出力例:
org.apache.commons:commons-io:2.11.0
variant “compile” [
analysis requested
]
— jvm-ecosystem-analysis (or transitive chain) —>
\— org.springframework.boot:spring-boot-starter-web:3.2.0
\— project :app
—
3. チーム開発で絶対に破綻させないための「依存関係ガバナンス設定」
個人の手作業でクリーンアップしても、次のプルリクエストでまた不要な依存関係が追加されては意味がない。CI/CDパイプラインやビルド設定レベルで「汚染を防ぐ仕組み(ガバナンス)」をコードとして組み込む。
ベストプラクティス構成例: Maven (`pom.xml`)
Mavenで未使用・不正な依存関係の混入をビルドエラー(Fail)にする設定だ。CI環境でこれを強制する。
goals>
- 解説: `failOnWarning>true` に設定することで、開発者が「使っていないライブラリ」や「隠れた依存関係」を混入させた瞬間、CI(GitHub ActionsやGitLab CIなど)のビルドが赤くなり、マージを物理的にブロックできる。
ベストプラクティス構成例: Gradle (`build.gradle.kts`)
Kotlin DSLを用いたモダンなGradle構成において、推移的依存関係の制御や、未使用ライブラリの検出を支援する設定。
plugins {
java
// 依存関係の解析に特化したコミュニティ標準プラグイン
id(“com.autonomousco.dependency-analysis”) version “1.31.0”
}
dependencyAnalysis {
issues {
all {
// 未使用の依存関係を発見した際のエラーレベルを設定 (severity: warn / error)
onUnusedDeclaredDependencies {
severity(“error”) // ビルドを失敗させる
}
// 宣言されていないが使用されている依存関係の検知
onUsedUndeclaredDependencies {
severity(“error”)
}
}
}
}
- 解説: `com.autonomousco.dependency-analysis` プラグインは、Java/Kotlinプロジェクトにおける依存関係最適化のデファクトスタンダードである。バイトコードを深く解析し、アノテーションのみで使用されているライブラリなども正確に判別してくれる。
—
4. 成果の測定:ビルド時間短縮と配布サイズ縮小の可視化
「本当に効果があるのか?」を数字で証明し、チーム全体のモチベーションを高めるための測定手順を公開する。
1. 配布サイズ(Fat JAR)の差分測定
最適化の前後で、ビルド成果物のサイズを正確に計測する。
最適化前のサイズ記録
ls -lh build/libs/app-all.jar
クリーンアップ実行後、再ビルドしてサイズを比較
./gradlew clean shadowJar
ls -lh build/libs/app-all.jar
【実務での実績値の例】
筆者が過去に担当した中規模マイクロサービス(Spring Bootベース)において、過去の遺産である数個の巨大ライブラリ(使われていないApache Commonsの古いバージョンや、巨大なXMLパーサー群)を排除したところ:
- Fat JARサイズ: `85MB` $\rightarrow$ `52MB` (約38%削減)
- Dockerイメージレイヤーの転送量: クラウド環境へのデプロイ速度が大幅に向上。
2. ビルド時間の短縮測定
ビルド時間のベンチマークを取るには、Gradleのプロファイリング機能やMavenのプロファイルを使用する。
Gradleのビルドプロファイルを取得し、依存関係スキャン・コンパイルにかかった時間を詳細分析
./gradlew build –profile
出力された `build/reports/profile/profile-.html` を開き、`Configuration` フェーズや `JavaCompile` タスクの実行時間が劇的に短縮されていることを確認する。クラスパス上のJARファイルが減ることで、ファイルI/Oのオーバーヘッドが軽減され、特にマルチプロジェクト構成において数秒〜数十秒単位のビルド高速化がもたらされる。
—
5. テックリードが現場に導入するためのアプローチ
いきなり全プロジェクトで `failOnWarning=true` を入れると、既存のビルドが一斉に壊れてチームがパニックに陥る。以下のステップで段階的に導入せよ。
1. フェーズ1(可視化): まずはCIのログ出力のみ(警告レベル)で `dependency:analyze` や `dependency-analysis` を導入し、現状の負債の大きさをチームにダッシュボードやチャットで共有する。
2. フェーズ2(除外リストの作成): フレームワークの仕様上どうしてもバイトコード解析に引っかからないものを `ignoredDependencies` に適切に登録する。
3. フェーズ3(厳格化): 新規追加されるコードに対してのみ、あるいはマスターブランチへのマージ条件として「エラー検知」を義務付ける。
隠れた依存関係の排除は、単なる「お片付け」ではない。それは、ソフトウェアの寿命を延ばし、セキュリティインシデントの確率を下げ、開発者の認知負荷を劇的に下げる最高への投資である。
今すぐあなたのプロジェクトのターミナルを開き、コマンドを叩いてみてほしい。そこには、まだ見ぬ「無駄なコードの山」が眠っているはずだ。