mvn assembly:assemblyとjava.lang.IncompatibleClassChangeError

mvn assembly:assemblyで作成したファイルをjava -jarで実行すると以下の例外が出る、という事象に悩まされてました。

Exception in thread "main" java.lang.IncompatibleClassChangeError: Implementing
class
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at java.security.SecureClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.access$000(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClassInternal(Unknown Source)
        at net.wrap_trap.utils.EntityService.<init>(EntityService.java:47)
        at net.wrap_trap.utils.BsonStore.initialize(BsonStore.java:276)
        at net.wrap_trap.utils.BsonStore.<init>(BsonStore.java:33)
        at net.wrap_trap.utils.FileStoredMap.<init>(FileStoredMap.java:26)
        at net.wrap_trap.collections.fsm.bench.Benchmark1.main(Benchmark1.java:9
)

IncompatibleClassChangeError?これまで遭遇したことがない例外だったので調べてみました。
IncompatibleClassChangeError (Java Platform SE 6)

Thrown when an incompatible class change has occurred to some class definition. The definition of some class, on which the currently executing method depends, has since changed.

クラス定義ファイル間の不整合が発生しているようですが、そんな変なクラスの変更したかな…。一旦cleanしてからリトライしましたが、事象が変わらず。

コンパイルからちょっと離れて、クラス間の定義で不整合が出るという点に着目してみたところ、2つ思い当たる事がありました。今回のモジュールはorg.mongodb.bsonに直接依存しており、mongo-java-driverに間接依存していました。mongo-java-driverのjarファイルの中にorg.bsonのクラスファイルが含まれており、それが直接依存しているorg.mongodb.bsonのクラスファイルとバージョンが合わない為、この例外が出ているのではないかと。

そしてもう1つ、mvn assembly:assemblyする際に、依存モジュールをunpackするようにしていました。

<assembly>
  <id>distribution</id>
  <formats>
    <format>zip</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <dependencySets>
    <dependencySet>
      <unpack>true</unpack>
      <scope>runtime</scope>
      <outputDirectory>/</outputDirectory>
    </dependencySet>
  </dependencySets>
</assembly>

unpackを指定すると、assembly時に依存モジュールのjarを展開してクラスファイルとしてモジュールに取り込みます。
今回の問題は、2つのモジュールにバージョンの異なるorg.bsonのクラスファイルがそれぞれ入っていており、展開される際にどちらかが上書きされてしまう為、不整合が起きていました。

依存モジュールでmongo-java-driverを使っているので、org.bsonを直接参照するのをやめ、代わりにmongo-java-driverを参照するようにしたところ、この例外は起こらなくなりました。dependencySetのunpackをtrueにする場合、この点に気をつける必要があります。