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」)

出力結果の読み方

依存ツリーの記号には意味があります。+---はその依存がさらに別の依存を引き込むことを示し、\---はそのセクションの最後の依存を表します。また、バージョン競合が解決された結果として選ばれたバージョンには*が付きます。ツリーを読むことで、「どのライブラリがどのバージョンで解決されたか」「どのライブラリがどの推移的依存を引き込んだか」を把握できます。問題のある推移的依存を発見したら、次のステップとしてexcludedependencyInsightで対処します。

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は宣言した依存の推移的解決にのみ影響し、他の依存が同じモジュールを引き込む場合は除外されません。バージョン競合の解決にはforcestrictlyを使用し、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()
}
  1. dependencyLockingをビルドスクリプトで有効化
  2. ./gradlew dependencies --write-locksでロックファイル生成
  3. 以降のビルドはロックファイルに記録されたバージョンで解決

ロック後に推移的依存のバージョンが変わるとビルドが失敗するため、サプライチェーンの変化をレビュー可能な差分として検知できます。(出典: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と依存関係検証を組み合わせてサプライチェーンリスクに対応する。