🐘

Gradle の設定ファイルとライブラリ管理とビルドバージョン設定

2025/03/02に公開

はじめに

この記事は、次の記事に入れようとしたら長くなりすぎた部分を切り出した記事です。

https://zenn.dev/suzuki_hoge/articles/2025-02-introduction-to-java-c9d1047a3f233f

Gradle の機能のうち、ライブラリ管理とビルドバージョンについてまとめました。

「普段の開発でメンテするのは主にこの辺りだろう」という経験と、「どうも似た設定が多くてよくわからないな」という経験をもとに整理対象を決めました。

なかなか複雑ですが、これくらい知って ( not 覚えて ) いればあまり困ることはなくなると思います。

設定ファイル

用途ごとにいくつかあるので、簡単にまとめます。

.gradle というファイルは Groovy という言語で設定しますが、最近は .gradle.kts というファイルを Kotlin で書けます。

settings.gradle.kts

プロジェクトの全体設定や構成設定を定義するファイルです。

https://docs.gradle.org/current/userguide/settings_file_basics.html

とてもシンプルなプロジェクトだと、名前くらいしか書いてなかったりします。

settings.gradle.kts
rootProject.name = "my-awesome-app"

settings.gradle.kts を読み書きすることは普段ほとんどありません。

この記事では取り上げませんが、Multi-Project [1] 構成にする場合は必須のファイルです。

https://docs.gradle.org/current/userguide/intro_multi_project_builds.html

gradle.properties

いわゆるプロパティファイルです。
プロジェクトルートの gradle.properties ファイルと、ホームディレクトリの ~/.gradle/gradle.properties ファイルがあります。

同じプロパティは ~/.gradle/gradle.properties の値が使われます。

プロジェクト全体で使う共有していい値は gradle.properties に書き、共有できない値は ~/.gradle/gradle.properties に書きます。

gradle.properties
fooCode=1234            # プロジェクトの設定
fooType=root            # プロジェクトの設定 ( デフォルト値 )
~/.gradle/gradle.properties
fooSecret=abcd          # 共有できないが必要な設定
fooType=home            # プロジェクトの設定を個人調整

読み込まれたプロパティは properties タスクで確認できます。

$ ./gradlew properties | grep foo | sort
fooCode: 1234          # プロジェクトの設定
fooSecret: abcd        # 個人の設定
fooType: home          # プロジェクトの設定を上書きした個人の設定

共有できない設定は、主に認証情報と性能設定です。
たとえば社内ライブラリをプライベートリポジトリに置いている場合は、GitHub の認証情報を設定する必要があります。
メモリの割当量などは秘匿情報ではないですが、ひとによって適切値が異なるため必要に応じて各個人で調整します。

設定した値は次のように build.gradle.kts で参照できます。

build.gradle.kts ( 抜粋 )
val fooCode: String by project
println("foo_code = $fooCode")

所定のパラメータは設定するだけで効果があります。

gradle.properties
org.gradle.jvmargs=-Xmx2048m -Xms512m

ほかのパラメータ名などは公式ドキュメントで確認できます。

https://docs.gradle.org/current/userguide/build_environment.html

settings.gradle.kts はあくまで Gradle のプロパティファイルであり、プログラムで使うプロパティ管理は別の方法を使います。
たとえば Spring Boot で環境ごとにデータベースの接続先をわけたいなどのケースでは、Spring のお作法に則り application-{profile}.yml を使います。

-P オプションと -D オプション

プロパティ設定は -P オプションや -D オプションでもできます。

$ ./gradlew properties -P fooType=p-option | grep foo | sort
fooType: p-option
$ ./gradlew properties -Dorg.gradle.project.fooType=d-option | grep foo | sort
fooType: d-option

-P オプションは Gradle のプロパティを指定するオプションで、ふたつの gradle.properties ファイルより優先されます。

-D オプションは JVM の設定をするオプションで、org.gradle.project. で始めると -P オプションと同じ結果になります。
Gradle の設定をするときは素直に -P オプションを使いましょう。

オプションによる指定は CI/CD で使ったりします。

環境変数

プロパティ設定は環境変数でもできます。

$ export ORG_GRADLE_PROJECT_fooType=env

$ ./gradlew properties | grep foo | sort
fooType: env

環境変数による指定は CI/CD で秘匿情報を渡すのに使ったりします。

build.gradle / build.gradle.kts

普段一番読み書きする設定ファイルです。

Gradle プロジェクトの基本的な設定や依存ライブラリの管理、テストやコンパイルの設定などほとんどのことを設定します。

https://docs.gradle.org/current/userguide/build_file_basics.html

https://docs.gradle.org/current/userguide/writing_build_scripts.html

gradle/libs.versions.toml

build.gradle.kts に直接依存ライブラリのバージョンを書かず、Version Catalogs という機能で TOML ファイルにライブラリ情報を集約できます。

https://docs.gradle.org/current/userguide/version_catalogs.html

この記事では取り上げませんが、Multi-Project の際に特に便利 [2] です。

まとめ

  • ✏️ .gradle.gradle.kts は読み替え可能
  • ✏️ settings.gradle.kts は Multi-Project を使わないならあまり書くことはない
  • ✏️ gradle.properties などで Gradle のプロパティ設定をする
  • ✏️ build.gradle.kts がメインの設定ファイル
  • ✏️ gradle/libs.versions.toml でライブラリ管理を一元化できる

依存ライブラリの設定

build.gradle.kts はいろいろな設定ができますが、その中で一番触るとしたらライブラリ管理の項目でしょう。
プロジェクトが進めば増えることもありますし、バージョンアップ対応しないといけないですからね。

いくつかあるので簡単に見てみます。

plugins

いきなりライブラリではなくプラグインですが、plugins ブロックについて確認します。

build.gradle.kts ( 抜粋 )
plugins {
    kotlin("jvm") version "2.1.10"

    id("org.springframework.boot") version "3.4.3"
    id("io.spring.dependency-management") version "1.1.7"
}

Gradle プラグインには、特定のタスクを追加したりライブラリの依存関係を管理してくれたりするものがあります。

kotlin() による指定は Kotlin 公式プラグインのための特別な文法で、それ以外のプラグインは id() で指定します。
kotlin("jvm")id("org.jetbrains.kotlin.jvm") のエイリアスです。

jvm プラグインは Kotlin コードのコンパイルやテストをできるようにするものです。

org.springframework.boot プラグインは Spring Boot の開発をサポートしてくれるもので、bootRun タスクはこれが提供してくれています。

io.spring.dependency-management プラグインは Spring 関係の依存ライブラリバージョンを統一的に管理してくれるプラグインです。

プラグインの情報は Gradle プラグインのページで確認できます。

https://plugins.gradle.org/plugin/io.spring.dependency-management

文字通り Gradle のプラグインに関する設定で、プログラムではなく Gradle の挙動そのものに影響します。

dependencies

プロジェクトのコードが使う外部ライブラリを定義するブロックです。
ここに書かれたライブラリは、ビルドのタイミングで自動ダウンロードされます。

一番読み書きするのがここですね。

build.gradle.kts ( 抜粋 )
repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.apache.commons:commons-lang3:3.17.0")
}


org.springframework.boot:spring-boot-starter-web ライブラリのバージョンは、io.spring.dependency-management プラグインがいい感じに決めてくれます。
org.springframework.boot プラグインが 3.4.3 なので、Spring 関係のライブラリは 3.4.3 に対応するバージョンが自動選択されます。
spring-xxx ライブラリはたくさんあるので、個別に手で書くより簡単だし安心です。

Spring 関係ではないライブラリは、当然自分でバージョンを指定します。
例に使った org.apache.commons:commons-lang3 ライブラリは、便利ユーティリティ集のよく使うライブラリです。

ライブラリの情報やバージョンは Maven Repository で確認できます。

https://mvnrepository.com/artifact/org.apache.commons/commons-lang3

mavenCentral() の指定は、ライブラリを Maven Repository から探すという指定です。
「社内ライブラリがプライベートリポジトリにある」というようなケースでは、探してほしい場所を Maven Central の下に追記します。

buildscript

プロダクトコードではなくビルドスクリプト ( build.gradle.kts ) で使う依存ライブラリを定義するブロックです。

次の設定は、Gson ライブラリを使う foo タスクを定義しています。

build.gradle.kts
plugins {
    // kotlin("jvm")
}

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("com.google.code.gson:gson:2.8.8")
    }
}

tasks.register("foo") {
    doLast {
        val map = mapOf("id" to 2, "name" to "foo")
        val s = com.google.gson.Gson().toJson(map)
        println(s)
    }
}

jvm プラグインは Gson を使えるようにしてしまうので無効化しています。

buildscript で Gson ライブラリを追加したので、foo タスクの処理部で Gson() を使えています。
import を一番上に書くと buildscript より先に評価されてエラーになってしまうため、完全修飾名で使うか importbuildscript より下に書きます。

$ ./gradlew foo

> Task :foo
{"id":2,"name":"foo"}

このように foo タスクが動く状況でも、dependencies ブロックに Gson が追加されていなければ次のコードはエラーになります。

Main.kt
import com.google.gson.Gson          // Unresolved reference 'google'

fun main() {
    val map = mapOf("id" to 1, "name" to "foo")
    val s = Gson().toJson(map)
    println(s)
}

ビルドスクリプトを書くのは、なんらかのチェック処理や CI/CD のための自作タスクを作るときです。
タスクを自作するほかだと、DB マイグレーション ( e.g. Flyway ) のタスクを動かすために DB 関係のライブラリが必要になるなどのケースがあります。

とはいえ、あまり buildscript に大量の依存設定をすることはないです。

まとめ

  • ✏️ plugins で追加するプラグインとは、Gradle の挙動そのものに影響するもの
  • ✏️ dependencies でプログラムが使うライブラリを管理する
  • ✏️ buildscript では build.gradle.kts で使うライブラリを管理する

依存の構成

dependencies ブロックでライブラリへの依存を設定する際、implementation 以外にもいくつかのキーワードがあります。
適切に使い分けることで無駄な依存が減り、ビルド結果が小さくなったりビルド時間が短くなったりという恩恵が得られます。

implementation に対する testImplementation のように、それぞれには test~ というテストコードのための依存設定もあります。

https://docs.gradle.org/current/userguide/dependency_configurations.html#two-dependency-configurations

implementation

implementation で依存したライブラリは、コンパイルに使われ JAR にも含まれます。

Spring Boot など、大体のライブラリは implementation で設定します。

api

api で依存したライブラリは、implementation と同じくコンパイルに使われビルド結果にも含まれます。
implementation と違うのは、再エクスポートされる点です。

implementation で依存したライブラリは、再エクスポートされません。

たとえば Gradle プロジェクト MyApp とライブラリ Lib1 Lib2 があり、MyApp は Lib1 を依存追加しています。
このとき Lib1 が Lib2 に implementation で依存していても、MyApp から Lib2 が使えるようにはなりません。

しかし Lib1 が Lib2 に api で依存していると、MyApp から Lib2 が使えるようになります。



「Lib1 のメソッド引数が Lib2 のオブジェクト」みたいな状況では、Lib1 は Lib2 とセットで使える必要があるからです。
そういう意味で API ( Application Programming Interface ) と言うのでしょう。 [3]

セットで必要になるライブラリをまとめて得られる反面、管理が複雑化したりコンパイル時間が長くなったりします。

自分の Gradle プロジェクトが社内ライブラリや Gradle サブプロジェクトではなくただのスタンドアロンアプリケーションであれば、api の利用を検討する必要はありません。

compile

挙動としては api に近いですが、非推奨時代を経てもう使えなくなりました。
再エクスポートしたい場合は api を使います。

かなり古い書き方なので、見かけたら ( api ではなく ) implementation に書き直して使いましょう。

runtimeOnly

runtimeOnly で依存したライブラリは、コンパイルに使われず実行時にだけ使われます。

よくあるケースとしては、JDBC ドライバやログライブラリの依存設定に使います。
「自分のコード上では Logger という interface としてしか存在しないが、動くときはなんらかの具象クラスが必要」みたいなケースです。

わかりやすくいうと「import はしないけど必要なライブラリ」は runtimeOnly で指定するということです。
implementation を使うよりコンパイル時間の短縮になります。

compileOnly

compileOnly で依存したライブラリは、コンパイルにだけ使われビルド結果には含まれません。

よくあるケースとしては、Lombok のようなアノテーションプロセッサの依存設定に使います。
Lombok はクラス変数の String name@Getter アノテーションをつけると、function String getName() メソッドを生成してくれます。
仕組みとしてはコンパイル時にアノテーション部分を通常実装と同じ形になるようクラスファイルへねじ込んでおり、コンパイルすると Lombok はなくなります。

そのままですが「コンパイルにだけ使うライブラリ」は compileOnly で指定するということです。
implementation を使うよりビルド結果の縮小になります。

まとめ

  • ✏️ implementation で依存追加するのが基本
  • ✏️ api は必要になるまで気にしなくていい
  • ✏️ runtimeOnlyimport しないけど必要なライブラリの追加に使う
  • ✏️ compileOnly は実行には不要なライブラリの追加に使う

依存ツリーの確認

https://docs.gradle.org/current/userguide/viewing_debugging_dependencies.html

Gradle で追加したライブラリは、dependencies タスクで確認できます。
ツリーで子要素になっているものは、依存先の依存で入ったものです。

$ ./gradlew dependencies

> Task :dependencies

------------------------------------------------------------
Project 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
------------------------------------------------------------

compileClasspath - Compile classpath for main.
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.21
|    |    +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21
|    |    \--- org.jetbrains:annotations:13.0
|    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21
|         \--- org.jetbrains.kotlin:kotlin-stdlib:1.8.21 (*)
+--- org.springframework.boot:spring-boot-starter-web -> 3.1.1
|    +--- org.springframework.boot:spring-boot-starter:3.1.1
|    |    +--- org.springframework.boot:spring-boot:3.1.1
|    |    |    +--- org.springframework:spring-core:6.0.10
|    |    |    |    \--- org.springframework:spring-jcl:6.0.10
|    |    |    \--- org.springframework:spring-context:6.0.10
|    |    |         +--- org.springframework:spring-aop:6.0.10
|    |    |         |    +--- org.springframework:spring-beans:6.0.10
|    |    |         |    |    \--- org.springframework:spring-core:6.0.10 (*)
|    |    |         |    \--- org.springframework:spring-core:6.0.10 (*)
|    |    |         +--- org.springframework:spring-beans:6.0.10 (*)
|    |    |         +--- org.springframework:spring-core:6.0.10 (*)
|    |    |         \--- org.springframework:spring-expression:6.0.10
|    |    |              \--- org.springframework:spring-core:6.0.10 (*)

# 以下 5000 行ほど切り捨て

よく目にする記号の (*) は、二度目の登場なのでサブツリーの表示を省略するというマークです。

もうひとつよく見る -> は、Gradle がバージョンを決めたという意味です。
次の出力は「spring-boot-starter-web3.1.1 にした」と伝えています。

+--- org.springframework.boot:spring-boot-starter-web -> 3.1.1

build.gradle.kts が次のようになっているので、プラグインにより Gradle が決定したということです。

build.gradle.kts ( 抜粋 )
plugins {
    id("org.springframework.boot") version "3.1.1"
    id("io.spring.dependency-management") version "1.1.6"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
}

次のようにライブラリのバージョンをプラグインとずらしても、3.2.0 ではなく 3.1.1 が使われます。

build.gradle.kts ( 抜粋 )
plugins {
    id("org.springframework.boot") version "3.1.1"
    id("io.spring.dependency-management") version "1.1.6"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web:3.2.0")
}

次の出力は「spring-boot-starter-web3.2.0 って書いてあったけど 3.1.1 にした」と伝えています。

+--- org.springframework.boot:spring-boot-starter:3.2.0 -> 3.1.1

プラグインに限らず、複数ライブラリのバージョン衝突によりこのような自動解決が発生するのは珍しくありません。

次の出力は「flyway-mysql:9.22.1flyway-core:9.22.1 を使うはずが、ほかとの兼ね合いで 9.16.3 にした」と伝えています。

+--- org.flywaydb:flyway-mysql:9.22.1
     \--- org.flywaydb:flyway-core:9.22.1 -> 9.16.3


これが問題になるかはケースバイケースです。
上のような形で内部ライブラリのバージョンが明らかにずれているケースは、問題になりやすいです。
ライブラリの更新を放置しているとライブラリ間の整合がどんどん取れなくなり、最悪のケースではコンパイルできなくなります。 [4] [5]

基本的には最新バージョンや公式が明言している対応バージョンでちゃんと統一しましょう。

たとえば Spring Boot だと Releases に情報があります。

https://github.com/spring-projects/spring-boot/releases/tag/v3.4.0

build.gradle.kts ( 抜粋 )
plugins {
    // 安定版最新
    id("org.springframework.boot") version "3.4.3"
    // 安定版最新
    id("io.spring.dependency-management") version "1.1.7"
}

dependencies {
    // プラグインにまかせる
    implementation("org.springframework.boot:spring-boot-starter-web")
    // Releases などを見て調べて書く
    implementation("org.flywaydb:flyway-mysql:10.20.1")
}

こうやってメンテすると整います。

+--- org.springframework.boot:spring-boot-starter-web -> 3.4.3
# 略
+--- org.flywaydb:flyway-mysql:10.20.1
     \--- org.flywaydb:flyway-core:10.20.1

さらに情報が必要になったら、そのときは公式サイトをどうぞ。

https://docs.gradle.org/current/userguide/viewing_debugging_dependencies.html

Java のバージョン設定

このセクションは Java のコンパイルや実行、Gradle Toolchain などを整理します。
コンパイル・実行や Toolchain の概要については、こちらの記事も参考にしてください。

https://zenn.dev/suzuki_hoge/articles/2025-02-introduction-to-gradle-456e16d3197d75

Gradle ( Java コンパイル ) におけるバージョン指定には似たような設定があるため、それぞれ確認します。

sourceCompatibility, targetCompatibility

sourceCompatibility では、どのバージョンの言語仕様としてコンパイルするか指定します。
これを 17 にすると、17 でリリースされた構文や機能がコンパイルできます。
端的に言えば、コーディングするときの言語バージョンです。

targetCompatibility では、生成されるクラスファイルが動作する JVM のバージョンを指定します。
これを 17 にすると、JVM 17 以上で動くようになります。
端的に言えば、実行環境の JVM バージョンです。

それぞれ javac コマンドの -source オプションと -target オプションに相当します。

次のメイン文を使いながら、確認します。
ローカルマシンの JDK は 17 で、まず Kotlin は抜きにして Java のプロジェクトで整理します。

Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(System.getProperty("java.version"));
        System.out.println(System.getProperty("java.home"));
    }
}

sourceCompatibility < targetCompatibility < JDK

build.gradle ( 抜粋 )
java {
    sourceCompatibility = JavaVersion.VERSION_15
    targetCompatibility = JavaVersion.VERSION_16
}

実行結果は次のようになります。

$ ./gradlew run

17.0.6
/Users/xxx/xxx/corretto-17.0.6/Contents/Home

実行時の java.version プロパティが 17.0.6 なので、JDK は 17 です。
JAVA_HOME を見るとローカルマシンにインストールされている JDK が使われています。

クラスファイル ( JAR ) を作って、中の major version を確認すると 60 になっています。

$ ./gradlew clean build
$ unzip build/libs/foo.jar -d /tmp/out
$ javap -v /tmp/out/foo/Main.class | grep major
major version: 60

これは targetCompatibility が 16 だから [6] です。

「15 の文法で書かれているコードを」「16 の JVM で動くように」「17 の JDK でコンパイルした」ということになります。

この設定のまま Record Class のような新機能をコードで使うと、コンパイルエラーになります。
コンパイラが 17 でも、コードの言語仕様を 15 としているからです。

public record Foo() {
       ^
  (レコードを有効にするには-source 16以上を使用してください)

targetCompatibility 省略

targetCompatibility は、省略すると sourceCompatibility と同じになります。
15 の言語仕様を使っているなら 15 の JVM で動かすだろう、ということです。

build.gradle ( 抜粋 )
java {
    sourceCompatibility = JavaVersion.VERSION_15
}
major version: 59

sourceCompatibility > targetCompatibility ( エラー )

sourceCompatibility > targetCompatibility はコンパイルエラーになります。
15 の機能を使っているのに 14 の JVM では動かせません。

build.gradle ( 抜粋 )
java {
    sourceCompatibility = JavaVersion.VERSION_15
    targetCompatibility = JavaVersion.VERSION_14
}
警告:ソース・リリース15にはターゲット・リリース15が必要です

sourceCompatibility > JDK ( エラー )

sourceCompatibility を JDK より高いバージョンにすることは当然できません。
コンパイラの知らない機能が使われているコードは、コンパイルできません。

build.gradle ( 抜粋 )
java {
    sourceCompatibility = JavaVersion.VERSION_21
}
エラー: 21は無効なソース・リリースです

まとめ

  • ✏️ sourceCompatibility はコーディングに使える言語機能のバージョン
  • ✏️ targetCompatibility は実行環境のバージョン
  • ✏️ sourceCompatibilitytargetCompatibility は運用環境の JVM と同じでいい
  • ✏️ どちらも JDK より高いバージョンにはできない
  • ✏️ どちらもコンパイラ ( javac ) のオプション

Toolchain

Toolchain はプロジェクトのビルドに使用する JDK を管理する Gradle の機能です。

https://docs.gradle.org/8.10.2/userguide/toolchains.html

プロジェクトで使えるインストール済み JDK の確認

javaToolchains タスクで選択できる JDK を確認できます。

$ ./gradlew javaToolchains

 + Options
     | Auto-detection:     Enabled
     | Auto-download:      Enabled

 + Amazon Corretto JDK 17.0.6+10-LTS
     | Location:           /Users/xxx/xxx/corretto-17.0.6/Contents/Home
     | Language Version:   17
     | Vendor:             Amazon Corretto
     | Architecture:       x86_64
     | Is JDK:             true
     | Detected by:        IntelliJ

 + Amazon Corretto JDK 21.0.5+11-LTS
     | Location:           /Users/xxx/xxx/corretto-21.0.5/Contents/Home
     | Language Version:   21
     | Vendor:             Amazon Corretto
     | Architecture:       aarch64
     | Is JDK:             true
     | Detected by:        IntelliJ

 + Eclipse Temurin JDK 1.8.0_362-b09
     | Location:           /Library/xxx/xxx/temurin-8.jdk/Contents/Home
     | Language Version:   8
     | Vendor:             Eclipse Temurin
     | Architecture:       x86_64
     | Is JDK:             true
     | Detected by:        MacOS java_home

プロジェクトで使う JDK の指定

さきほど確認したとおり、プロジェクトのバージョン指定がローカルマシンの JDK ( 17 ) より高いと、コンパイルできません。

build.gradle ( 抜粋 )
java {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
}
エラー: 21は無効なソース・リリースです

build.gradle に次のように書いておくと、そのプロジェクトは JDK 21 でビルドされるようになります。

build.gradle ( 抜粋 )
java {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21

    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

実行にも JDK 21 が使われています。

$ ./gradlew run

21.0.5
/Users/xxx/xxx/corretto-21.0.5/Contents/Home

Gradle を実行する JDK さえあれば、プロジェクトのビルドはプロジェクトの定めた JDK で行われるようになります。

sourceCompatibility, targetCompatibility, toolchain は基本的に必ず指定しましょう。

プロジェクトで使う JDK の自動インストール

Gradle 8.0 以前は、toolchain で指定されたバージョンがローカルマシンになければ自動インストールされていました。
Gradle 8.0 からはベンダが増えたことや柔軟性を持たせるなどの理由で、ダウンロードリポジトリ ( Toolchain Resolver Plugins ) の指定が必須になりました。

今手元には 22 がないため、toolchain22 を指定すると次のようなエラーが発生します。

$ ./gradlew run
> Task :compileJava FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileJava'.
> Error while evaluating property 'javaCompiler' of task ':compileJava'.
   > Failed to calculate the value of task ':compileJava' property 'javaCompiler'.
      > Cannot find a Java installation on your machine matching this tasks requirements: {languageVersion=22, vendor=any vendor, implementation=vendor-specific} for MAC_OS on x86_64.
         > No locally installed toolchains match and toolchain download repositories have not been configured.

* Try:
> Learn more about toolchain auto-detection at https://docs.gradle.org/8.10.2/userguide/toolchains.html#sec:auto_detection.
> Learn more about toolchain repositories at https://docs.gradle.org/8.10.2/userguide/toolchains.html#sub:download_repositories.
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 2s
1 actionable task: 1 executed

自動ダウンロードもできるようにするには、settings.gradle にプラグイン追加しておく必要があります。

settings.gradle
plugins {
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0'
}

こうすると JDK が ~/.gradle/jdks に自動ダウンロードされます。

$ ./gradlew run

22.0.2
/Users/xxx/.gradle/jdks/eclipse_adoptium-22-x86_64-os_x.2/jdk-22.0.2+9/Contents/Home

まとめ

  • ✏️ sourceCompatibilitytargetCompatibility を運用環境の JVM と同じにする
  • ✏️ toolchain で同じバージョンの JDK を指定する
  • ✏️ settings.gradle.kts で自動ダウンロードできるようにする

Kotlin のバージョン設定

このセクションは Kotlin のコンパイルや実行、Gradle Toolchain などを整理します。

Java と基本の部分は同じなので、簡単にまとめます。

compilerOptions

次の設定は、kotlinCompile タスクにおける kotlin のコンパイルでコンパイラ ( kotlinc ) の -jvm-target オプションを指定しています。

kotlinCompile タスクで 16 を指定するには、プロジェクト全体も java ブロックで 16 に揃える必要があります。

build.gradle.kts ( 抜粋 )
java {
    sourceCompatibility = JavaVersion.VERSION_16
    targetCompatibility = JavaVersion.VERSION_16
}

tasks.withType<KotlinCompile> {
    kotlin {
        compilerOptions {
            jvmTarget = JvmTarget.JVM_16
        }
    }
}

ビルドされたクラスファイルの major version は 60 [7] になります。
要するに Java のコンパイラ ( javac ) における -target オプションに相当するものです。

major version: 60

16 の JVM で動くようにコンパイルしただけで、JDK はローカルマシンの 17 が使われています。

$ ./gradlew run

17.0.6
/Users/xxx/xxx/corretto-17.0.6/Contents/Home

Toolchain

Kotlin のビルドに使用する JDK を管理するには、kotlin ブロックで jvmToolchain を指定します。

build.gradle.kts ( 抜粋 )
kotlin {
    jvmToolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}
$ ./gradlew run

21.0.5
/Users/xxx/xxx/corretto-21.0.5/Contents/Home

まとめ

  • ✏️ sourceCompatibilitytargetCompatibility にあわせて compilerOptions も書く
  • ✏️ toolchain や自動ダウンロードについては Java と同じ

おわりに

さっと書いてしまうつもりでしたが、細かいところをちゃんと確認したら思ったより大変でした。
それだけに細かいところまであらためて確認できたので、満足感は得られました。

Gradle の設定はすこし複雑なのと、前提となるコンパイルや JVM などの理解が必要なので大変ですね。

脚注
  1. apicorewebbatch のように、Gradle プロジェクトをいくつかの Gradle プロジェクトにわける仕組み。コンパイル範囲が局所化でき、プロジェクトを跨いだ import のコントロールなどもできる。 ↩︎

  2. 複数のサブプロジェクトで使うライブラリのバージョンをひとつの TOML ファイルに書けるので、メンテがとても楽になる。 ↩︎

  3. この記事は設計論の記事ではないので詳細は割愛しますが、Lib1 の引数に Lib2 を使うかはちゃんと検討するべきです。Lib1 が「Lib2 使うのやめるわ」と言って引数を Lib3 に変更したら、MyApp は Lib2 のコードをすべて Lib3 に書き換えなければいけないからです。 ↩︎

  4. 実際ときどき見ます。「Lib1 が内部で利用している Lib2 を 9.0 だと思って呼んでいるのに、実際には Lib2 が 8.0 だった。呼んでいるメソッドは 9.0 でできたものだった。」「Lib3 を追加したいのに Lib1 が古すぎて追加すらできなかった。」はありがちです。 ↩︎

  5. ある意味、本当の最悪は「コンパイルできたけど実行エラーになった」かも。 ↩︎

  6. 60 - 44 = 16 ↩︎

  7. 60 - 44 = 16 ↩︎

Discussion