FAT JAR(もしくは über JAR)とは、Java アプリケーション本体と依存ライブラリ等を1つの JAR ファイルに「アセンブル」したものです。依存ライブラリを含めてパッケージングされており、単体で実行可能になっています。メインクラスを持つ FAT JAR であれば、以下のように特にクラスパスを設定をしなくても実行できます:
java -jar <jarfile>
-jar
オプションで起動したとき、JAR ファイル内に含まれるクラスファイルが唯一のユーザークラス・ソースとなります。FAT JAR には実行時に必要とされる全てのクラスが含まれているため、これで問題ありません。ファイル構成・起動コマンドがシンプルになるので、デプロイ時に便利ですよね1。
さて、sbt-assembly は FAT JAR を生成するための sbt プラグインです。Maven Assembly Plugin の sbt 版ですね。ためしに logback-classic
を libraryDependencies
に追加して sbt assembly
すると以下のようなエラーとなります。
[error] (assembly) deduplicate: different file contents found in the following:
[error] /root/.cache/coursier/v1/https/repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.4.4/logback-classic-1.4.4.jar:module-info.class
[error] /root/.cache/coursier/v1/https/repo1.maven.org/maven2/ch/qos/logback/logback-core/1.4.4/logback-core-1.4.4.jar:module-info.class
module-info.class
というファイルが重複してエラーになっているようですが、これはなんでしょう?
Java のモジュールシステム(Project Jigsaw)と module-info.class
Java SE 9 以降(LTS としては Java SE 11 以降)で導入された新しい2仕様であり、いわゆるクラスパス地獄だとか JAR 地獄といわれる問題を回避するために提案されました。モジュールシステムについてはこちらの記事に詳しいです:モジュールシステムを学ぶ
module-info.class
(ソースとしては module-info.java
) はモジュールの定義情報ファイルです。これがない場合は「自動モジュール」(モジュールパスにある場合。クラスパスの場合は「無名モジュール」)として、以下のように扱われます:
- すべてのパッケージを
exports
している - モジュールグラフに読み込まれたすべてのモジュールを
requires
している
なお、Scala / sbt はモジュールシステムをサポートしていないため、成果物は状況に応じて自動モジュールないしは無名モジュールとして認識されることになります。
assemblyMergeStrategy
でエラーを解消する
Java SE 8 準拠の環境の場合、モジュールシステムを認識しないので assemblyMergeStrategy を定義して破棄してしまえば問題ありません。
ThisBuild / assemblyMergeStrategy := {
case PathList(ps @ _*) if ps.last endsWith "module-info.class" =>
MergeStrategy.discard
case x =>
val oldStrategy = (assembly / assemblyMergeStrategy).value
oldStrategy(x)
}
Java SE 9 以降(LTS としては Java SE 11 以降)の場合でも、FAT JAR 内に必要なクラスがそろっているため、元のライブラリが持っていたモジュール情報は必要ありません。FAT JAR をライブラリとして利用する場合ならともかく、FAT JAR の主な用途となる起動可能な単一ファイルとして利用するケースなら大丈夫そうですね。
参考リンク
- What is an uber jar?
- javaコマンド
- sbt-assemblyを使用してjarを生成する際の同名ファイルのマージ指定方法
- JSR 376: Java™ Platform Module System
- モジュールシステムを学ぶ
- Javaのモジュールシステムを理解しよう(その1)
- Duplicate file exception occurs when packing graal vm #370
- Ignore module-info.class #391
- Play JSON library and sbt assembly merge error
- sbt assembly: deduplicate module-info.class
- JDK9: Support module-info.java #3368
- Support JEP-261 Module System #529
-
とはいいつつ、デプロイを易化するという意味では JDK ごと Docker コンテナに押し込んでしまう方法が普及した感もあります。 ↩
-
Java SE 11 が新しいといっていいものかどうかには諸説あります。 ↩