何が起きたのか
JARをデプロイし、クラスを実行するか、サーバーを起動した瞬間、JVMが以下のエラーをスローしました:
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/example/MyClass has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0
Javaはコンパイルされたすべての.classファイルにバージョン番号を埋め込みます。そのファイルを読み込むJREが、コンパイルに使われたJDKより古い場合、このエラーが発生します。JVMは推測しません——クラスのロードを完全に拒否します。
バージョン番号を解読する
エラーメッセージにはクラスファイルバージョンが含まれています。Javaリリースとの対応表:
- 45.0 → Java 1
- 52.0 → Java 8
- 55.0 → Java 11
- 56.0 → Java 12
- 57.0 → Java 13
- 58.0 → Java 14
- 59.0 → Java 15
- 60.0 → Java 16
- 61.0 → Java 17
- 62.0 → Java 18
- 63.0 → Java 19
- 64.0 → Java 20
- 65.0 → Java 21
つまりclass file version 61.0は、そのクラスがJava 17でコンパイルされたことを意味しますが、ランタイムは55.0(Java 11)までしか対応していません。バージョンの不一致が確定です。
診断:実際のバージョンを確認する
実行中のJREを確認する
java -version
出力例:
openjdk version "11.0.20" 2023-07-18
OpenJDK Runtime Environment (build 11.0.20+8)
OpenJDK 64-Bit Server VM (build 11.0.20+8, mixed mode)
コンパイルに使われたJDKを確認する
javac -version
javacが17で、javaが11——このギャップが問題の原因です。
クラスファイルを直接検査する
javap -verbose com/example/MyClass.class | grep "major version"
クラスを実行せずに、クラスファイルの正確なバージョンを出力します。
Linux:インストール済みのJDKをすべて確認する
update-alternatives --list java
# または
ls /usr/lib/jvm/
修正方法1 — JREをアップグレードする(推奨)
JARを再コンパイルできない場合は?サードパーティライブラリやCIビルドの成果物では一般的なケースです。対象マシンのJREを、コンパイルに使われたバージョンに合わせてアップグレードしましょう。
Ubuntu / Debian
sudo apt install openjdk-17-jdk
sudo update-alternatives --config java
macOS(Homebrew)
brew install openjdk@17
export JAVA_HOME=$(brew --prefix openjdk@17)
export PATH="$JAVA_HOME/bin:$PATH"
Windows
adoptium.netから対応するJDKをダウンロードしてインストールし、システム環境変数のJAVA_HOMEとPATHを更新してください。
確認する
java -version
# 17.x以上が表示されるはずです
修正方法2 — 古いJavaバージョンをターゲットに再コンパイルする
ソースコードを持っている場合は、--releaseを使ってランタイムがサポートするバージョン向けにコンパイルダウンしましょう。
javacを直接使用する
javac --release 11 com/example/MyClass.java
Maven(pom.xml)
<properties>
<maven.compiler.release>11</maven.compiler.release>
</properties>
またはコンパイラープラグインを使用する場合:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
Gradle(build.gradle)
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
Gradle 6.7以降ではツールチェーンが導入されました——バージョンを固定するより明確な方法です:
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
Spring Bootプロジェクト
# application.propertiesまたはpom.xmlのプロパティ
<java.version>11</java.version>
その後、再ビルドします:
./mvnw clean package
# または
./gradlew build
修正方法3 — 実行時に正しいJVMを指定する
複数のJDKがインストールされている場合は、システムデフォルトを完全に無視してフルパスを指定します:
/usr/lib/jvm/java-17-openjdk-amd64/bin/java -jar myapp.jar
または実行前にJAVA_HOMEを設定します:
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
java -jar myapp.jar
システムデフォルトを変更したくないCI/CDパイプラインやDockerコンテナで重宝します。
よくあるシナリオ:ベースイメージが間違っているDockerコンテナ
コンテナ化されたアプリでは頻繁に発生します。DockerfileがJava 17でコンパイルされたJARを実行するのにopenjdk:11を使用している場合——バージョンが最初から一致していません。
# 誤り
FROM openjdk:11-jre-slim
COPY target/myapp.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
# 正しい — コンパイルターゲットに合わせる
FROM eclipse-temurin:17-jre-alpine
COPY target/myapp.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
確認手順
- JREのバージョンがコンパイルターゲット以上であることを確認する:``` java -version
- 再コンパイルした場合、新しいクラスファイルのバージョンを確認する:```
javap -verbose target/classes/com/example/MyClass.class | grep "major version"
- アプリケーションを実行して、
UnsupportedClassVersionErrorが出なければ完了です:``` java -jar target/myapp.jar
- Dockerの場合:コンテナを再ビルドして実行し、起動ログを確認する。
## 得られた教訓
- **CI/CDでJavaバージョンを固定する。**パイプラインがJava 21でコンパイルし、本番環境がJava 11で動いている——このエラーはデプロイのたびに発生します。両方を同じバージョンに固定して、ずれが生じないようにしましょう。
- **`-source`/`-target`ではなく`--release`を使う。**古いフラグはバイトコードバージョンを設定するだけです。Java 11に存在しないJava 17のAPIを呼び出しても止めてくれません。`--release`はバイトコードバージョンとAPI利用可能性の両方を強制します。
- **サードパーティJARを確認する。**エラーが常に自分のコードを指しているわけではありません。スタックトレースに記載されたクラスに対して`javap -verbose`を実行し、どの依存関係から来たものかを特定してから、そのライブラリの最低Java要件を確認しましょう。
- **GradleツールチェーンとMavenラッパーは「自分の環境では動く」問題を解消します。**ローカルに何がインストールされているかに関わらず、宣言されたJavaバージョンでビルドが行われることを保証します。

