概要: Gradleを使ったJavaプロジェクトで欠かせない依存関係管理について、基本概念から実践的なコマンド、設定ファイルの書き方までを解説します。implementationとapiの使い分けや、不要な依存の除外方法、依存関係の固定化による再現性の向上まで、現場で役立つ知識を網羅しています。
Gradleの依存関係とは?基本概念とConfigurationの種類を解説
依存関係管理の基本構造
Gradleの依存関係管理は、「何を」「どこから」「どの範囲で」使うかを3つのブロックで定義します。ライブラリの指定はdependenciesブロック、取得元のリポジトリはrepositoriesブロック、適用範囲はConfiguration(設定)で管理します。Javaプロジェクトでは、Java PluginやJava Library Pluginを適用すると、コンパイルやテスト用のConfigurationが自動的に追加されます。これにより、コンパイル時に必要な依存と実行時にのみ必要な依存を区別して宣言できます。(出典:Gradle「Declaring Dependency Configurations」)
Configurationの種類と役割
Configurationは依存関係の「スコープ」を定義する仕組みです。Java/JVMプロジェクトで利用できる代表的なConfigurationを以下の表にまとめます。
| Configuration | コンパイル時 | 実行時 | 下流に公開 | 主な用途 |
|---|---|---|---|---|
| implementation | あり | あり | しない | デフォルト推奨。内部実装用 |
| api | あり | あり | する | 公開APIに型が現れる依存 |
| compileOnly | あり | なし | しない | コンパイル時のみ必要な依存 |
| runtimeOnly | なし | あり | しない | 実行時のみ必要な依存 |
| testImplementation | テスト時にあり | テスト時にあり | しない | テストコード用 |
旧compile設定との違い
過去に使われていたcompile設定は現在非推奨であり、implementationまたはapiに置き換えられています。compileは依存を下流のプロジェクトに過剰に公開してしまい、不要な再コンパイルを引き起こす問題がありました。implementationは依存を内部に隠蔽するため、依存ライブラリの変更が下流プロジェクトへ伝播せず、ビルド時間の短縮につながります。新規プロジェクトでは必ずimplementation系を使用してください。
要点:依存関係はdependenciesブロックで宣言し、Configurationで適用範囲を制御する。implementationは内部隠蔽、apiは公開、compileは非推奨。
Gradle dependenciesコマンドで依存関係ツリーを確認する方法
dependenciesタスクの基本
./gradlew dependenciesコマンドは、プロジェクトの依存関係ツリーを表示する標準タスクです。推移的依存関係を含む全体像を確認でき、バージョン競合が発生している箇所は*マークで示されます。デフォルトではルートプロジェクトの依存のみを表示するため、サブプロジェクトを確認する場合は./gradlew :app:dependenciesのようにプロジェクト名を明示する必要があります。(出典:Gradle「DependencyReportTask」)
出力結果の読み方
依存ツリーの記号には意味があります。+---はその依存がさらに別の依存を引き込むことを示し、\---はそのセクションの最後の依存を表します。また、バージョン競合が解決された結果として選ばれたバージョンには*が付きます。ツリーを読むことで、「どのライブラリがどのバージョンで解決されたか」「どのライブラリがどの推移的依存を引き込んだか」を把握できます。問題のある推移的依存を発見したら、次のステップとしてexcludeやdependencyInsightで対処します。
dependencyInsightと便利なオプション
特定の依存が「なぜ・どのバージョンで」選ばれたかを調査するには、./gradlew dependencyInsight --dependency <group:name>を使用します。依存関係調査に使える主なコマンドを以下にまとめます。
dependencies --configuration runtimeClasspath:指定Configurationのツリーを表示dependencyInsight --dependency <group:name>:特定依存の解決理由を調査dependencies --write-locks:依存関係ロックファイルを生成・更新--refresh-dependencies:キャッシュを無視して全依存を再解決--offline:キャッシュのみで解決(ネットワークアクセス回避)
要点:dependenciesタスクで依存ツリーを表示し、*マークで競合を確認。dependencyInsightで特定依存の解決理由を調査できる。
Gradle dependenciesの書き方|implementationとapiの使い分け
implementationの基本
implementationは最も一般的に使う依存宣言です。モジュール内部の実装にのみ使用するライブラリを宣言し、依存は下流プロジェクトに公開されません。これにより、ライブラリのバージョンを変更しても、それに依存するプロジェクトの再コンパイルを最小限に抑えられます。Java Pluginを適用したプロジェクトでは、以下のように記述します。
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
}
バージョンは記事執筆時点の例であり、実際に使用する際は最新版を確認してください。
apiの役割と使用タイミング
apiは、公開APIのシグネチャに型が現れる依存に対して使用します。例えば、公開メソッドの戻り値や引数にJacksonの型を使う場合、その型は利用者にも必要になるためapiで宣言します。apiを使うにはjava-libraryプラグインの適用が必要です。
plugins {
id 'java-library'
}
dependencies {
api 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
}
apiを使いすぎると下流の再コンパイル範囲が広がりビルドが遅くなるため、原則として公開APIに型が現れる場合に限定してください。(出典:Gradle「Declaring Dependency Configurations」)
implementationとapiの比較
両者の違いを以下の表で比較します。
| 評価軸 | implementation | api |
|---|---|---|
| 依存の公開範囲 | モジュール内部のみ | 下流プロジェクトにも公開 |
| ビルド速度への影響 | 変更時の再コンパイル範囲が狭い | 変更時の再コンパイル範囲が広い |
| 必要なプラグイン | java | java-library |
| 推奨される用途 | 内部実装用(デフォルト) | 公開APIに型が現れる場合のみ |
要点:implementationをデフォルトにし、apiは公開APIに型が現れる依存に限定する。apiの使いすぎはビルド遅延の原因。
Gradle dependencies excludeで不要な推移的依存を除外する方法
推移的依存とexcludeの目的
Gradleは依存ライブラリがさらに依存するライブラリ(推移的依存)を自動的に解決します。しかし、セキュリティ脆弱性を含むライブラリや、使用しない不要なモジュールが推移的に入り込むことがあります。このような場合にexcludeを使用して、特定の推移的依存を依存グラフから除外できます。excludeはgroupとmoduleの組み合わせで指定し、宣言した依存からの推移的解決時のみに適用されます。(出典:Gradle「Declaring Dependency Configurations」)
excludeの書き方
excludeは依存宣言内に記述します。以下は、Spring BootのWebスターターからTomcatを除外する例です。
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
}
この記述により、spring-boot-starter-webが引き込むspring-boot-starter-tomcatとその配下の推移的依存が除外されます。代わりにJettyやUndertowを使用したい場合などに有効です。
exclude使用時の注意点
excludeは便利ですが、以下の点に注意が必要です。まず、除外したモジュールが必要な機能を提供していた場合、実行時エラーやClassNotFoundExceptionが発生する可能性があります。また、excludeは宣言した依存の推移的解決にのみ影響し、他の依存が同じモジュールを引き込む場合は除外されません。バージョン競合の解決にはforceやstrictlyを使用し、excludeは不要なモジュール自体を取り除く場合に限定して使用するのが適切です。
要点:excludeは不要な推移的依存を除外するが、実行時エラーのリスクを伴う。除外後は動作確認が必須。
Gradle dependenciesを安全に管理する!constraintsとロックの活用
バージョン競合の解決とconstraints
Gradleは複数のバージョンが依存グラフに現れた場合、デフォルトで最高バージョンを選択します。これはMavenの「直近優先(nearest first)」とは異なる解決方法です。特定のバージョンに固定したい場合は、constraintsブロックやstrictlyを使用します。
dependencies {
constraints {
implementation('org.apache.commons:commons-lang3') {
version {
strictly '3.12.0'
}
}
}
}
これにより、推移的依存が異なるバージョンを要求しても、指定バージョンに固定できます。
依存関係ロックで再現性を確保
依存関係ロックは、解決結果を固定バージョンに記録し、ビルドの再現性を高める機能です。dependencyLockingを有効にし、--write-locksでロックファイルを生成します。
dependencyLocking {
lockAllConfigurations()
}
dependencyLockingをビルドスクリプトで有効化./gradlew dependencies --write-locksでロックファイル生成- 以降のビルドはロックファイルに記録されたバージョンで解決
ロック後に推移的依存のバージョンが変わるとビルドが失敗するため、サプライチェーンの変化をレビュー可能な差分として検知できます。(出典:Gradle「Dependency Locking」)
Version Catalogと依存関係検証の併用
安全な依存関係管理には、以下の3層のアプローチが推奨されます。
| 層 | 機能 | 役割 |
|---|---|---|
| 宣言 | Version Catalog | バージョンを一元管理し、タイプセーフなアクセサを提供 |
| 固定 | 依存関係ロック | 解決結果を固定し、再現性を確保 |
| 検証 | 依存関係検証 | チェックサムやPGP署名でバイト列の完全性を保証 |
Version Catalogはgradle/libs.versions.tomlでバージョンを一元管理し、依存関係検証はgradle/verification-metadata.xmlでアーティファクトの完全性を検証します。ロックが「どのバージョンか」を固定するのに対し、検証は「取得したバイト列が本物か」を保証するため、役割が異なります。(出典:Gradle「Version Catalogs」「Dependency Locking」)
要点:constraintsでバージョン競合を制御し、ロックで再現性を確保。Version Catalogと依存関係検証を組み合わせてサプライチェーンリスクに対応する。
まとめ
よくある質問
Q: Gradleのdependenciesコマンドで何が確認できますか?
A: Gradleのdependenciesコマンドを実行すると、プロジェクトが依存しているライブラリの一覧と、その推移的依存関係をツリー形式で確認できます。依存関係の競合が発生している場合には、選択されたバージョンがアスタリスク(*)付きで表示されるため、問題の調査に役立ちます。
Q: Gradleでimplementationとapiはどう使い分ければよいですか?
A: implementationは、依存ライブラリがモジュール内部の実装のみで使用される場合に指定します。一方、apiは、モジュールの公開API(外部に公開するクラスのメソッドシグネチャなど)でそのライブラリの型を使用する場合に指定します。apiを使用すると依存が下流のモジュールに公開され、再コンパイルの範囲が広がるため、使いすぎには注意が必要です。
Q: Gradleのdependencies excludeの書き方を教えてください。
A: 依存関係から特定の推移的依存を除外するには、dependenciesブロック内で依存関係を宣言する際に、excludeを指定します。例えば、implementation(‘com.example:library:1.0’) { exclude group: ‘org.example’, module: ‘unwanted-module’ }のように記述します。groupとmoduleの両方を指定して除外するのが一般的です。
Q: Gradleで依存関係のバージョン競合が発生した場合、どう解決すればよいですか?
A: Gradleのデフォルトの動作では、競合した場合に最も高いバージョンが選択されます。競合を制御するには、resolutionStrategyでfailOnVersionConflict()を指定してビルドを失敗させたり、forceやstrictlyを使用して特定のバージョンに固定する方法があります。また、依存関係ツリーを確認して、不要な推移的依存をexcludeで除外することも有効です。
Q: Gradleで依存関係の再現性を高めるにはどうすればよいですか?
A: 依存関係の再現性を高めるには、バージョンカタログ(gradle/libs.versions.toml)でバージョンを一元管理し、依存関係ロック(dependency locking)を使用して解決結果を固定する方法が効果的です。さらに、依存関係検証(dependency verification)を有効にすることで、ダウンロードしたライブラリのチェックサム検証が行われ、サプライチェーン攻撃のリスクを軽減できます。
