---
title: "ツールチェーン(toolchain)とは？構成要素と言語別の実例・GradleのJava Toolchain設定"
url: "https://www.issoh.co.jp/tech/details/7073/"
published: 2025-06-03
updated: 2026-10-17
categories: ["開発"]
publisher: "株式会社一創"
---

# ツールチェーン(toolchain)とは？構成要素と言語別の実例・GradleのJava Toolchain設定

ツールチェーン（toolchain、ツールチェインとも表記）とは、ソースコードを実行できる成果物に変換するまでに順番に使う開発ツールの一式です。前処理・コンパイル・アセンブル・リンクの各ツールが、前工程の出力を次工程の入力として受け取る鎖（chain）のようにつながっているため、この名前で呼ばれます。この記事では構成要素と、手元のコンパイラで工程を確かめる方法、クロスコンパイルのターゲットトリプル、GNU・Rust・Go・フロントエンドの実例、GradleのJava Toolchain設定とsourceCompatibility・JAVA\_HOMEとの関係を、2026年10月時点の版と実際に動かした結果で整理します。

## まとめ

- ツールチェーンは、プリプロセッサ・コンパイラ・アセンブラ・リンカ・標準ライブラリ・デバッガなど、ソースを実行形式にするまでのツール群の全体を指す。コンパイラはその1工程にすぎない。
- `clang -###`を付けると、`cc`という1コマンドの裏でコンパイラ本体とリンカが別々のプロセスとして呼ばれる様子を確認できる。
- クロスコンパイルでは`arm-none-eabi`のようなターゲットトリプルでツールチェーンを選ぶ。コンパイラがそのCPUに対応していても、ターゲット用のヘッダ・ライブラリ・リンカが揃わなければ実行ファイルは作れない。
- Rustは`rust-toolchain.toml`、Goは`go.mod`のtoolchain行と`GOTOOLCHAIN`、JVMはGradle・MavenのToolchain機能で、プロジェクトごとに使う版を固定できる。
- GradleのJava Toolchainは`languageVersion`で宣言し、手元に無い版はsettings側のfoojayリゾルバで自動取得する。古いJava向けに出すときはsourceCompatibilityではなく`options.release`を使う。Gradle 9.8.1でToolchain 21とsourceCompatibility 17を併用すると、Java 21のAPIを呼ぶコードがビルドを通り、JDK 17で実行した時点で`NoSuchMethodError`になった。
- DevOpsツールチェーンは、計画から監視までのツール連携を指す別の用法である。

## ツールチェーン(toolchain)とは：定義と工程のつながり

C言語のビルドでは、プリプロセッサがヘッダを展開し、コンパイラがアセンブリコードを出力し、アセンブラがオブジェクトファイルに変換し、リンカが標準ライブラリと結合して実行ファイルを作ります。決まった順序で連結して使うこれらの開発ツールの集合がツールチェーンです。[Wikipediaの「ツールチェーン」](https://ja.wikipedia.org/wiki/%E3%83%84%E3%83%BC%E3%83%AB%E3%83%81%E3%82%A7%E3%83%BC%E3%83%B3)も、あるツールの出力が次のツールの入力になる連鎖を語源として挙げています。

ツールチェーンが指すのは個々のツールの寄せ集めではなく、互いの版と形式の整合が取れた一組です。コンパイラが出力するオブジェクトファイルの形式をリンカが読めなければ、どちらも単体では正常でもビルドは通りません。ツールチェーンをひとまとまりで配布・指定するのは、組み合わせた版を1か所で管理し、ずれを起こしにくくするためです。それでも実行先のOSやCPUに合う部品かは別に確かめる必要があり、その例はクロスコンパイルの章で示します。

## ツールチェーンを構成する要素

コンパイル型言語のツールチェーンは、おおむね次の部品でできています。Linuxで広く使われるGNU系と、Clangを中心とするLLVM系で、同じ役割のツール名を並べました。

| 工程・部品    | GNU系              | LLVM系                | 入力→出力             |
| -------- | ----------------- | -------------------- | ----------------- |
| 前処理      | gcc -E（通常はcc1に統合） | clang -E             | .c → .i           |
| コンパイル    | gcc（cc1）          | clang（-cc1）          | .i → .s または中間表現   |
| アセンブル    | as                | clang内蔵アセンブラ         | .s → .o           |
| リンク      | ld                | lld                  | .o＋ライブラリ → 実行ファイル |
| アーカイブ    | ar                | llvm-ar              | .o群 → .a          |
| 検査       | objdump・nm        | llvm-objdump・llvm-nm | バイナリ → 一覧         |
| デバッガ     | gdb               | lldb                 | 実行中のプロセス          |
| C標準ライブラリ | glibc             | OS側のlibcを使うのが一般的     | リンク時に結合           |

ここに依存関係と実行順序を管理するビルドツール（Make・CMake・Gradle・Mavenなど）が加わると、コマンド1つで全工程が走ります。ビルドツールは工程を呼び出す指揮者で、実際に変換するコンパイラやリンカの集合がツールチェーンです。GradleのJava Toolchain機能は、ビルドツールの側から「どのツールチェーンを使うか」を宣言する仕組みにあたります。

## clangで確かめるコンパイラドライバの呼び出し工程

普段打つ`cc`や`gcc`は、コンパイラそのものではなく、各工程のツールを順に呼び出すドライバです。macOSのApple clang 21.0.0で、2行のhello.cを使って確かめました。`-###`は実行せずに呼び出す予定のコマンドだけを表示するオプションです。

```
$ cat hello.c
#include <stdio.h>
int main(void){puts("hello");return 0;}
$ clang -### hello.c -o hello 2>&1 | grep -c '^ "'
2
$ clang -save-temps hello.c -o hello
$ ls
hello  hello.bc  hello.c  hello.i  hello.o  hello.s
```

通常のビルドで起動されるのは2プロセスだけでした。1つ目の`clang -cc1`が前処理・コンパイル・アセンブルをまとめて行い、2つ目のリンカ（`/Library/Developer/CommandLineTools/usr/bin/ld`）が実行ファイルを作ります。`-save-temps`で中間ファイルを残すよう指示すると、工程が5つに分かれて見えます。

| 順 | 呼ばれたツール      | 主な指定           | 出力            | 工程            |
| - | ------------ | -------------- | ------------- | ------------- |
| 1 | clang -cc1   | \-E            | hello.i（567行） | 前処理           |
| 2 | clang -cc1   | \-emit-llvm-bc | hello.bc      | 中間表現（LLVM IR） |
| 3 | clang -cc1   | \-S            | hello.s       | アセンブリ生成       |
| 4 | clang -cc1as | なし             | hello.o       | アセンブル         |
| 5 | ld           | なし             | hello         | リンク           |

2行のソースが前処理後に567行へ膨らむのは、`#include <stdio.h>`がSDKのヘッダを展開するためです（行数はSDKの版で変わります）。ヘッダもリンカもコンパイラの外にある部品で、どの版のSDKとリンカを組み合わせるかまで含めてツールチェーンです。コンパイラがあるのにビルドが通らないときは、`-###`で呼ばれるツールのパスを表示し、`-save-temps`でどの工程の出力まで作られたかを見ると、止まった部品を特定できます。

## クロスツールチェーンとターゲットトリプル

ツールチェーンという語が最も重みを持つのは、開発機と実行機のCPUやOSが違うクロスコンパイルの場面です。x86\_64のPCでARMマイコン向けのプログラムを作るなら、PC上で動いてARM向けの成果物を出す一式が必要になります。これがクロスツールチェーンです。

### ターゲットトリプルの読み方

クロスツールチェーンは、出力先を表すターゲットトリプルで区別します。[Clangのクロスコンパイル解説](https://clang.llvm.org/docs/CrossCompilation.html)は一般形を`<arch><sub>-<vendor>-<sys>-<env>`と説明し、[GCCのconfigure説明](https://gcc.gnu.org/install/configure.html)は正規の構成名を`cpu-company-system`の3部分としています。名前に「トリプル」とありますが、実際の文字列は3〜4要素です。

| トリプル                  | CPU              | OS        | 用途                 |
| --------------------- | ---------------- | --------- | ------------------ |
| arm-none-eabi         | 32ビットARM         | なし（ベアメタル） | Cortex-Mなどのマイコン    |
| arm-linux-gnueabihf   | 32ビットARM         | Linux     | ハードウェア浮動小数点のLinux機 |
| aarch64-linux-gnu     | 64ビットARM         | Linux     | ARMサーバ・ボード         |
| x86\_64-apple-darwin  | x86\_64          | macOS     | Intel Mac（実測機のホスト） |
| thumbv7em-none-eabihf | ARM Thumb（v7E-M） | なし        | Rustのマイコン向けターゲット   |

ベンダー部分を省いた書き方が多いため、各要素の位置は一定しません。`clang --target=arm-none-eabi -print-target-triple`を実行すると`arm-unknown-none-eabi`が返り、省かれていたベンダー欄が`unknown`で補われた正規形を確かめられます。ARMとx86の命令セットの違いは[ARMアーキテクチャとx86の違い](/tech/details/10467/)で扱っています。

### クロスコンパイルで不足するヘッダとリンカの実測

Clangは1つのバイナリで複数のターゲットへコード生成できます。では、macOSのclangに`--target`を渡すだけでLinux向けのプログラムが作れるのか。64ビットARMのLinux向けに、前章のhello.cと、ヘッダを使わない1行のadd.c（`int add(int a, int b) { return a + b; }`）で試すと、3段階で結果が分かれました。

```
$ clang --target=aarch64-linux-gnu -c hello.c -o hello_arm.o
hello.c:1:10: fatal error: 'stdio.h' file not found
$ clang --target=aarch64-linux-gnu -ffreestanding -nostdinc -c add.c -o add.o
$ file add.o
add.o: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), not stripped
$ clang --target=aarch64-linux-gnu add.o -nostdlib -o add
ld: unknown options: -EL --hash-style=gnu --eh-frame-hdr -dynamic-linker
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

add.cなら、ARM64のELFオブジェクトまでは作れます。止まったのはその前後です。`stdio.h`はターゲット用のヘッダ群（sysroot）が無いので見つからず、リンクはmacOS用のldがLinux向けのオプションを理解できずに失敗しました。クロスツールチェーンを揃えるとは、ターゲット用のコンパイラ設定・ヘッダとlibc・リンカを1組で用意することです。組込み開発でArmが配布するArm GNU Toolchainや、Linuxディストリビューションの`gcc-aarch64-linux-gnu`パッケージを使うのは、この組を自分で集めずに済ませるためです。ツールチェーンそのものをソースから組み上げる過程は[Linux From Scratch（LFS）の構築手順](/tech/details/10028/)が具体的です。

### CMakeのツールチェーンファイル

CMakeでは、ターゲットごとの設定を1つのファイルにまとめ、`cmake -DCMAKE_TOOLCHAIN_FILE=arm-none-eabi.cmake`のように渡します。[CMAKE\_TOOLCHAIN\_FILEの公式説明](https://cmake.org/cmake/help/latest/variable/CMAKE%5FTOOLCHAIN%5FFILE.html)は、この変数をクロスコンパイル時にコマンドラインで指定するものとしています。

```
# arm-none-eabi.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
```

最後の行は、独自のフラグやリンカスクリプトなしでは実行ファイルをリンクできないツールチェーン（ベアメタル向けに多い）で要ります。[cmake-toolchainsのマニュアル](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html)は、こうしたツールチェーンでは`CMAKE_TRY_COMPILE_TARGET_TYPE`を`STATIC_LIBRARY`にして、CMakeの事前チェックで実行ファイルのリンクを試させないよう案内しています。

## 分野・言語別のツールチェーンの実例

### GNUツールチェーンとLLVM

GNUツールチェーンは、GCC（コンパイラ）、GNU Binutils（as・ld・objdumpなど）、glibc（C標準ライブラリ）、GDB（デバッガ）を中心とする一式です。2026年10月時点の最新は[GCC 16.2（2026年8月7日）](https://gcc.gnu.org/)と[GNU Binutils 2.47](https://sourceware.org/binutils/)（2026年7月26日告知）です。DebianやUbuntuの`build-essential`パッケージは、gcc・g++・make・libc6-devなど、ネイティブのビルドに要る一式をまとめて入れます。

LLVM側の最新は[LLVM 23.1.3（2026年10月6日）](https://github.com/llvm/llvm-project/releases)です。Linuxカーネルも長くGNUツールチェーンでビルドされてきましたが、[カーネル公式ドキュメント](https://docs.kernel.org/kbuild/llvm.html)にClang/LLVMでのビルド手順が載っています。GCC専用ではありません。

### Rustのrustupとrust-toolchain.toml

Rustでは、ツールチェーンの管理が公式ツールrustupに組み込まれています。stable・beta・nightlyの3チャネルがあり、stableの最新は[1.99.1（2026年10月15日）](https://doc.rust-lang.org/stable/releases.html)です。リポジトリに次のファイルを置くと、そのディレクトリでは指定の版が使われます。

```
# rust-toolchain.toml
[toolchain]
channel = "1.99.1"
components = ["rustfmt", "clippy"]
targets = ["thumbv7em-none-eabihf"]
```

どの版を使うかの優先順位は、[rustupのOverrides](https://rust-lang.github.io/rustup/overrides.html)によると、`cargo +beta build`のようなコマンドライン指定、環境変数`RUSTUP_TOOLCHAIN`、`rustup override`によるディレクトリ設定、`rust-toolchain.toml`、既定のツールチェーンの順です。ただしディレクトリ設定とファイル指定の2つは現在のディレクトリへの近さでも比べられ、近い階層の`rust-toolchain.toml`は遠い階層のディレクトリ設定より優先されます。`targets`に書いたクロスコンパイル用のターゲットは、ホスト用に加えて導入されます。

rustup 1.29.0で、未導入の1.90.0を書いた`rust-toolchain.toml`のディレクトリに入って確かめました。`RUSTUP_AUTO_INSTALL=0`を付けると`error: toolchain '1.90.0-x86_64-apple-darwin' is not installed`で止まり、付けずに`rustup`のサブコマンドを実行すると、確認なしで1.90.0のダウンロードが始まりました。CIで意図しない版の取得を避けたいなら、この環境変数で止められます。

### GoのGOTOOLCHAINとgo.mod

Goは1.21で、`go.mod`の`toolchain`行と環境変数`GOTOOLCHAIN`によって版を選ぶ仕組みを入れました。[Go公式のToolchains解説](https://go.dev/doc/toolchain)では、既定の`auto`は手元の版で足りなければ新しい版をPATHから探し、無ければダウンロードします。`local`は常に同梱の版を使い、`path`はダウンロードしません。最新の[go1.27.2（2026年10月8日）](https://go.dev/doc/devel/release)で試した結果です。

```
$ cat go.mod
module example.com/t

go 1.28
$ GOTOOLCHAIN=local go build
go: go.mod requires go >= 1.28 (running go 1.27.2; GOTOOLCHAIN=local)
```

逆に`toolchain go1.27.0`と書いたモジュールを1.27.2で開くと、`go version`は1.27.2のままでした。toolchain行は推奨する最低版で、手元の版が新しければ下げません。設定値ごとの挙動は[GOTOOLCHAINでGoのバージョンを切り替える仕組み](/tech/details/5635/)で詳しく扱っています。

### フロントエンドのツールチェーン

フロントエンドでは機械語へのコンパイラやリンカを用意しません（機械語への変換はブラウザのJavaScriptエンジンが担います）。それでもツールチェーンと呼ぶのは、ソースを配信できる成果物に変える工程が複数ツールの連鎖になっているからです。TypeScriptやBabelが構文を変換し、Viteや[esbuildのようなバンドラ](/tech/details/16017/)がモジュールをまとめ、ESLintやBiomeが静的解析を行います。

パッケージマネージャの版の固定も同じ考え方です。`package.json`の`packageManager`欄に版を書き、Corepackがその版を用意します。ただし[Node.js 25以降はCorepackが本体に同梱されない](https://nodejs.org/docs/latest-v25.x/api/corepack.html)ため、別途の導入が必要です。手順は[corepackの後継コマンドとNode.js 25以降の導入](/tech/details/2765/)にまとめています。

## DevOpsツールチェーンという別の用法

「DevOpsツールチェーン」は、ここまでのコンパイル系とは対象が違います。計画（Jiraなど）、ソース管理（Git）、ビルド、テスト、デプロイ、監視（Datadogなど）まで、開発のライフサイクル全体をつなぐツール群の連携を指す言葉です（[AtlassianのDevOpsツールチェーン解説](https://www.atlassian.com/ja/devops/devops-tools/choose-devops-tools)）。

前工程の出力が次工程の入力になる構造は同じで、違うのは粒度です。コンパイル系が1回のビルドの内部を指すのに対し、DevOps系は組織の開発プロセス全体を指します。求人票や製品資料で「ツールチェーンの統合」とあれば、並んでいるツール名がJiraやJenkinsなのか、GCCやリンカなのかを見ると、どちらの意味かを切り分けられます。GitHub Actionsでビルドからテストまでをつなぐ例は[GitHub Actionsでビルド・自動テストを設定する方法](/tech/details/2497/)で扱っています。

## GradleのJava Toolchain設定

JVM系のビルドでは、GradleのJava Toolchainがツールチェーン指定の中心です。[Gradle公式のToolchainsページ](https://docs.gradle.org/current/userguide/toolchains.html)は、大半の利用者にこの方法を勧めています。

### Gradleでの基本設定

プロジェクトが使うJDKの版を`build.gradle.kts`で宣言します。javaプラグインを適用したうえでjavaブロックに書きます。

```
plugins {
    java
}

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

これでGradle自体を動かすJDKが何であっても、コンパイルとテスト実行はJDK 21で行われます。Gradle本体の実行には別にJVMが要り、最新の[Gradle 9.8.1（2026年10月7日）](https://docs.gradle.org/9.8.1/release-notes.html)はJava 27をデーモンとToolchainの両方でサポートしています（対応は9.8.0から）。[互換性表](https://docs.gradle.org/current/userguide/compatibility.html)では、9.8.1を動かせるJVMは17〜27、Java 25対応は9.1.0から、Java 26対応は9.4.0からです。

### JDKが見つからないときの挙動と自動ダウンロード

手元に無い版を宣言するとどうなるか。Gradle 9.8.1をJDK 25だけの環境で動かし、Toolchainに21を指定して`compileJava`を実行すると、次のエラーで止まりました。

```
> Cannot find a Java installation on your machine (Mac OS X 26.6.2 x86_64) matching:
  {languageVersion=21, vendor=any vendor, implementation=vendor-specific, nativeImageCapable=false}.
  Toolchain download repositories have not been configured.
```

自動ダウンロードには、ダウンロード元を定義するリゾルバプラグインが必要です。実務ではfoojayのリゾルバを使い、これはsettingsプラグインなので`settings.gradle.kts`に書きます。[Plugin Portal](https://plugins.gradle.org/plugin/org.gradle.toolchains.foojay-resolver-convention)の最新は1.0.0（2025年5月19日）です。

```
// settings.gradle.kts
plugins {
    id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
```

追加して再実行すると、Eclipse Temurin 21.0.12.1がGradleユーザーホーム（既定は`~/.gradle`）の`jdks`配下へ展開され、ビルドが通りました。`gradle javaToolchains`の一覧では、このJDKが`Detected by: Auto-provisioned by Gradle`、起動に使ったJDK 25が`Detected by: Current JVM`と表示されます。一方、`~/.local/java`に置いていたJDK 26は一覧に出ませんでした。自動検出の対象はSDKMAN!などの既知の場所だけで、独自の場所に置いたJDKは`gradle.properties`の`org.gradle.java.installations.paths`で教える必要があります。

### KotlinのjvmToolchain

Kotlin Gradle Pluginでは、kotlinブロックで同じ宣言ができます。最新のKotlinは[2.4.21（2026年10月8日）](https://kotlinlang.org/docs/releases.html)です。

```
kotlin {
    jvmToolchain(21)
}
```

[Kotlin公式](https://kotlinlang.org/docs/gradle-configure-project.html)によると、jvmToolchainはコンパイラに`-jdk-home`を設定し、jvmTargetを明示していなければ`compilerOptions.jvmTarget`をツールチェーンのJDKの版に合わせます。kotlin側で設定したToolchainはJavaのコンパイルタスクにも反映されるため、JavaとKotlinが混在するプロジェクトでも宣言は1か所で済みます。

### Mavenのtoolchains.xml

Mavenでは、使えるJDKを`~/.m2/toolchains.xml`に登録し、maven-toolchains-plugin（最新3.3.0）でプロジェクトが要求する版を選びます。Gradleと違い、宣言だけでJDKを取ってくるのではなく、あらかじめ入れたJDKを登録しておく方式です。

```
<toolchains>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>21</version>
      <vendor>temurin</vendor>
    </provides>
    <configuration>
      <jdkHome>/usr/lib/jvm/temurin-21</jdkHome>
    </configuration>
  </toolchain>
</toolchains>
```

pom.xmlではプラグインの`toolchain`ゴールに`<jdk><version>21</version></jdk>`を渡します。[Maven 4](https://maven.apache.org/whatsnewinmaven4.html)は実行にJava 17を要求しますが、別のJDKでビルドする用途のToolchainsはそのまま使え、[既定の配置場所](https://maven.apache.org/ref/4-LATEST/api/maven-api-toolchain/toolchains.html)も`~/.m2/toolchains.xml`のままです。

## sourceCompatibilityとoptions.releaseの使い分け

Toolchain・sourceCompatibility・targetCompatibility・KotlinのjvmTargetは役割が重なって見えますが、決めるものはそれぞれ違います。使うJDKと成果物の対象Javaが同じ版なら、新規のプロジェクトで書くべきはToolchainだけです。Gradle公式の推奨は「大半はToolchain、厳密な互換性が要るなら`--release`、高度な場合は両者の併用」で、sourceCompatibilityとtargetCompatibilityは「必要でない限り避ける」と明記されています。

| 設定項目                      | 決めるもの                | Toolchain設定時の扱い       |
| ------------------------- | -------------------- | --------------------- |
| toolchain languageVersion | コンパイル・テストに使うJDK本体    | これを設定する               |
| options.release           | 言語水準・バイトコード・参照できるAPI | 古いJava向けに出すときだけ足す     |
| sourceCompatibility       | 使える言語構文の版            | Toolchainの値が入るので不要    |
| targetCompatibility       | 出力するバイトコードの版         | Toolchainの値が入るので不要    |
| jvmTarget（Kotlin）         | Kotlinが出すバイトコードの版    | jvmToolchainの値が入るので不要 |

sourceCompatibilityを避けるべき理由は、Gradle 9.8.1で再現できます。Java 21で追加された`List.getFirst()`を呼ぶクラスを`src/main/java/A.java`に置き、Toolchainを21にしたままsourceCompatibilityを17に下げてビルドしました。

```
// src/main/java/A.java
import java.util.List;
public class A { public static void main(String[] a){ System.out.println(List.of(1,2,3).getFirst()); } }

// build.gradle.kts
plugins { java }
java {
    toolchain { languageVersion = JavaLanguageVersion.of(21) }
    sourceCompatibility = JavaVersion.VERSION_17
}

$ gradle compileJava --debug | grep "Compiler arguments"
... Compiler arguments: -source 17 -target 17 -d ...
$ ~/.gradle/jdks/eclipse_adoptium-17-x86_64-os_x.2/jdk-17.0.20.1+1/Contents/Home/bin/java -cp build/classes/java/main A
Exception in thread "main" java.lang.NoSuchMethodError: 'java.lang.Object java.util.List.getFirst()'
```

ビルドは成功し、クラスファイルのmajor versionは61（Java 17）でした。Gradleがjavacに渡したのは`-source 17 -target 17`で、`--release`ではありません。構文とバイトコードの版だけが17になり、参照するAPIはJDK 21の標準ライブラリのままなので、Java 17で動かした瞬間に落ちます。sourceCompatibilityの行を消して`options.release = 17`に替えると、同じコードはコンパイル時に`エラー: シンボルを見つけられません`で止まりました。古いJavaで動く成果物を配るときの指定は次の形です。

```
tasks.withType<JavaCompile>().configureEach {
    options.release = 17
}
```

sourceCompatibilityとtargetCompatibilityの出番は、Toolchain機能が使えない古いビルド環境くらいです。既存プロジェクトからこれらを消してToolchainへ移すときは、CIに該当するJDKが無いと前章のエラーで落ちるため、foojayリゾルバの設定と同時に行ってください。JDKを上げると[Lombok](/tech/details/3300/)のようなアノテーションプロセッサが追随できずにコンパイルが落ちることがあるので、Toolchainの版に対応した版へ合わせて上げます。チームごとにJDKやビルド設定がばらついた既存システムを作り直す段階なら、[フルスクラッチ開発](/service/system/fullscratch/)の中でビルド環境の版の固定から設計する方法もあります。

## JAVA\_HOME・PATHの役割と複数JDKの切り替え

### JAVA\_HOMEとPATHの役割の違い

JAVA\_HOMEはJDKのインストール先のルートを指す環境変数です（`/usr/lib/jvm/temurin-21`のように、binやlibを含む親ディレクトリを指す。`bin/java`そのものではない）。Gradle・Maven・多くのIDEは、使うJDKをこの変数から探します。

PATHは、OSがコマンド名から実行ファイルを探す検索パスの一覧です。`java -version`に応答するJDKはPATHで決まります。両者は独立しているので、PATHにはJDK 17のbinが通っているのにJAVA\_HOMEはJDK 11を指す、という食い違いが起こり、`java -version`は17なのにGradleは11で動く状態になります。手動でJDKを入れ替える運用なら、JAVA\_HOMEを更新し、PATHには`$JAVA_HOME/bin`を通して二重管理を避けます。

### 複数JDKの切り替えツール

プロジェクトごとに要求されるJDKが違う場合は、バージョン管理ツールで切り替えます。

| ツール     | 対応OS                | 特徴                    |
| ------- | ------------------- | --------------------- |
| SDKMAN! | macOS・Linux・WSL     | JDKに加えGradle・Mavenも管理 |
| jEnv    | macOS・Linux         | Windowsネイティブ非対応       |
| mise    | macOS・Linux・Windows | NodeやPythonも一元管理      |

使い方は[SDKMAN!でのJavaの切り替えと.sdkmanrc](/tech/details/11413/)と[mise.tomlでツールを束ねる導入手順](/tech/details/17621/)にあります。ただし、Gradleでビルドする用途に限れば、Java Toolchainを設定した時点で手元の切り替えはほぼ不要です。切り替えツールが要るのは、Gradle自体を動かすJDKを揃えたいときと、Toolchainに対応しないツールを併用するときです。

## Androidプロジェクトでのツールチェーン設定

Androidのビルドでは、版を指定する層が3つに分かれていて混乱しやすくなります。Android Gradle Plugin（AGP）を動かすJDK、アプリのソースをコンパイルするJDK、コンパイル時に参照するAndroid SDKのAPIレベル（compileSdk）です。最新のAGP 9.4.1が属する[9.4系の要件](https://developer.android.com/build/releases/agp-9-4-0-release-notes)は、Gradle 9.6.0以上とJDK 17以上です。

```
android {
    compileSdk = 37
}
kotlin {
    jvmToolchain(21)
}
```

compileSdkはAPIレベル（37はAndroid 17）で、JDKの版ではありません。[AndroidのJDKに関する公式ページ](https://developer.android.com/build/jdks)は、ライブラリの支えが要るJava言語機能を使えるかはcompileSdkで決まるとしたうえで、Toolchainの指定を常に勧め、Toolchainの版がsourceCompatibilityとtargetCompatibilityの既定値になると説明しています。Kotlin 2.2未満を使う場合だけは、`kotlinOptions`の`jvmTarget`も合わせて書く必要があります。

## よくある質問

### ツールチェーンとツールチェインはどちらの表記が正しいですか？

どちらもtoolchainのカタカナ表記で、指すものは同じです。辞書サイトのe-Wordsは「ツールチェーン」を見出しにして「ツールチェイン」を併記しています。英語でtool chainと2語に分けて書かれる場合も同じ意味です。

### コンパイラとツールチェーンは何が違いますか？

コンパイラはツールチェーンを構成する工程の1つです。C/C++の場合、コンパイラ（とアセンブル）が作るのはオブジェクトファイルまでで、実行ファイルにするにはリンカと、実行環境に応じたライブラリが要ります。`clang -###`で確かめると、`cc`の1コマンドの裏でコンパイラ本体とリンカが別々に起動しています。ドライバが全工程を代行するため、両者は同一視されやすくなっています。

### Gradleで「Cannot find a Java installation on your machine」と出るのはなぜですか？

Toolchainで宣言した版のJDKがマシン上で見つからず、自動ダウンロードも設定されていない状態です（古いGradleでは「No matching toolchains found」という文言でした）。該当する版のJDKを入れるか、`settings.gradle.kts`にfoojayリゾルバを追加します。JDKが入っているのに見つからない場合は、独自の場所に置いている可能性があるので`org.gradle.java.installations.paths`で場所を指定します。

### Gradle Toolchainを設定すればJAVA\_HOMEは不要になりますか？

ビルド対象のJDKを決める目的では不要です。コンパイルとテストに使うJDKはビルドスクリプトが決めるため、JAVA\_HOMEが何を指していても結果は変わりません。ただしGradleデーモンを起動するJVMは既定でJAVA\_HOMEから探されます。[Daemon JVM criteria](https://docs.gradle.org/current/userguide/gradle%5Fdaemon.html)を`updateDaemonJvm`タスクで設定すれば、こちらもJAVA\_HOMEより優先して宣言どおりのJVMが使われます。

### KotlinのjvmToolchainとGradleのjava toolchainはどちらを書けばよいですか？

どちらか一方で足ります。Kotlin公式によると、kotlinブロックで設定したToolchainはJavaのコンパイルタスクにも反映され、javaブロックで設定したToolchainはKotlinのコンパイルにも使われます。ただし、Kotlinコンパイラ自体は常にGradleデーモンのJDKの上で動き、Toolchainが変えるのは参照するJDKと出力するバイトコードの版です。

## 関連記事

- [GOTOOLCHAINとは｜Go Toolchainでバージョンを自動切り替えする仕組みと設定方法](/tech/details/5635/)
- [SDKMAN!の使い方｜Javaのインストール・切り替えと.sdkmanrc、Windows・macOSの注意点](/tech/details/11413/)
- [corepack prepareは非推奨？後継のinstall -g・use・enableとpnpmの版管理、Node.js 25以降の導入](/tech/details/2765/)
- [GitHub Actionsでビルド・自動テストを設定する方法｜CI/CDワークフローの作り方](/tech/details/2497/)
- [ARMアーキテクチャとは？x86との違い・Armv9の世代とarm64移行の確認手順](/tech/details/10467/)

---

出典: [ツールチェーン(toolchain)とは？構成要素と言語別の実例・GradleのJava Toolchain設定](<https://www.issoh.co.jp/tech/details/7073/>)（株式会社一創）
