きっかけ
ある日、Java アプリケーションの macOS 向け配布パッケージを jpackage で作成しようとしたところ、エラーで処理が止まりました。
調べた結果、JAVA_HOME に指定していたのが Homebrew でインストールした OpenJDK だったことが根本原因でした。
Temurin に切り替えた途端、エラーは消えました。
この経験をきっかけに、Homebrew の OpenJDK と他のディストリビューションの JDK が何を違いとして持っているのかを整理しました。
ディレクトリ構造の違い
Homebrew OpenJDK — Linux 風 Flat 構造
Homebrew は Linux のパッケージ管理規約に倣った構造でインストールします。
/opt/homebrew/opt/openjdk/
├── bin/
├── include/
├── lib/
│ └── jvm/
└── libexec/
└── openjdk.jdk/
└── Contents/ ← 内部に隠蔽
└── Home/
Apple が期待する macOS バンドルの構造は Contents/ を最上位に持ちますが、Homebrew はこれを libexec/ 以下に隠蔽しています。
/Library/Java/JavaVirtualMachines/ へのシンボリックリンクは張られるものの、jpackage のランタイム検出はリンク先の実体パスを参照するため、整合性が崩れることがあります。
Distribution JDKs(Temurin / Oracle / Microsoft)— Apple Bundle 構造
Cask 経由でインストールされる JDK は /Library/Java/JavaVirtualMachines/ に正規の macOS バンドルとして配置されます。
/Library/Java/JavaVirtualMachines/temurin-21.jdk/
└── Contents/
├── Home/ ← JAVA_HOME
│ ├── bin/
│ └── lib/
├── Info.plist ← macOS バンドルメタデータ
└── MacOS/
この構造は /usr/libexec/java_home による JDK 検出、jpackage のランタイムバンドル解決、codesign / notarytool のいずれとも完全に適合しています。
jpackage における具体的な問題
| 問題 | 原因 | 影響範囲 |
|---|---|---|
| ランタイム検出失敗 | Info.plist の欠如または不正なパス |
--runtime-image 指定時 |
| コード署名エラー | .app バンドル構造との不整合 |
Apple Silicon 含む全環境 |
| Notarization 拒否 | Apple ツールチェーンが期待する JDK 構造との乖離 | 配布パッケージ全般 |
jlink モジュールエラー |
symlink 越しのモジュールパス解決の失敗 | カスタムランタイム生成時 |
シンボリックリンクとパス解決
Homebrew は /Library/Java/JavaVirtualMachines/ への symlink を提供していますが、jpackage 内部では realpath() による実体パス解決が行われます。
その結果、--runtime-image や --jdk-path に渡したパスが期待する JDK バンドル構造と一致せず、エラーが発生します。
以下のコマンドで実体パスを確認できます。
$ readlink -f $(which java)
# Homebrew: /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java
$ /usr/libexec/java_home -V
# Temurin: /Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home
推奨される代替手段
jpackage を使うプロジェクトでは、以下のいずれかを使用してください。
- Temurin(Eclipse Adoptium) — 最も実績が多く、CI/CD との相性も良好です
- Microsoft Build of OpenJDK — Azure DevOps との統合が優れています
- Oracle JDK — 商用サポートが必要な場合に適しています
まとめ
Homebrew の OpenJDK は開発・テスト・CI 実行用途では問題なく機能します。
しかし macOS 配布パッケージの生成(jpackage)、コード署名、Notarization が絡む場面では、Apple バンドル構造に準拠した JDK を使うべきです。
用途に応じて JDK を使い分けることが、macOS ネイティブ開発における現実的なアプローチです。