Error [has been compiled by a more recent version of the Java Runtime (class file version 61.0)]

UnsupportedClassVersionError: class file version 61.0 の直し方(Java)

FIX SUMMARY verified
Applies when
eclipse-temurin:17-jdkclass compiled with a newer JDK executed on an older JVM (release 17 -> Java 8)

Verified: reproduced in eclipse-temurin:17-jdk, then the has been compiled by a more recent version of the Java Runtime (class file version 61.0) signature was gone after the fix (exit 0).

Java 8 のまま動いていたアプリを、新しい JDK でビルドし直した。あるいは CI の JDK だけが上がった。すると実行時にこれが出ます。

Exception in thread "main" java.lang.UnsupportedClassVersionError: App 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 52.0

同じ原因が、class file version 65.0up to 55.0 という数字違いでも出ます。Gradle からは Unsupported class file major version 61 という短い形で出ます(こちらは別の機構です。後述)。

まず読むべきは 61 ではなく、末尾の 52.0

このメッセージには数字が2つあり、それぞれ別のものを指しています

  • class file version 61.0 … その .classビルドした JDK
  • up to 52.0 … いま実行している JVM が読める上限

つまり「Java 17 でビルドしたものを、Java 8 の JVM で動かそうとした」という意味です。多くの人が 61.0 をそのまま検索しますが、直すための手がかりは末尾の 52.0 のほうにあります。そこに、動かしている JVM の正体が書いてあるからです。

クラスファイル版と Java のバージョンの対応は次のとおりです(実測値)。

Java のバージョンclass file version
852
1155
1761
2165

up to 52.0 と言われたなら、実行しているのは Java 8up to 55.0 なら Java 11 です。ここまで分かれば、あとは2つに1つになります。

直し方は2つ。ビルド側を下げるか、実行側を上げるか

1. 実行側の JVM を上げる(原則こちら)

新しい JDK でビルドしたのには理由があるはずで、その場合に直すのは実行環境のほうです。

# 動かしている JVM を確認する(up to 52.0 なら 1.8.x が出るはず)
java -version

Docker なら、実行イメージのタグを揃えます。ビルドは 17 なのに実行ベースが 8 のまま、という食い違いがよくある形です。

# 実行ステージのベースを、ビルドに使った JDK と揃える
FROM eclipse-temurin:17-jre

CI なら、テストや実行を行うジョブの JDK も上げてください(actions/setup-javajava-version など)。ビルドのジョブだけ上げて、実行のジョブが古いまま、という取りこぼしがこのエラーの典型的な出方です。

2. ビルド側を古い JVM に合わせる(実行環境を動かせないとき)

本番の JVM が 8 から動かせないなら、成果物のほうを 8 向けに出します。--release を使うと、バイトコードの版だけでなく API も 8 のものに制限されるので、「ビルドは通ったのに実行時に NoSuchMethodError」という次の事故を防げます。

<!-- pom.xml -->
<properties>
  <maven.compiler.release>8</maven.compiler.release>
</properties>

Gradle なら次の形です。

// build.gradle.kts
java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(8))
    }
}

javac を直接叩くなら javac --release 8 です。古い -source / -target だけの指定は、新しい JDK の API を参照したままバイトコードの版だけ下げてしまうので避けてください。--release はまさにその穴を塞ぐために用意されました。

なぜ JVM は新しいクラスファイルを読まないのか

JVM は .class の先頭にあるメジャー版を読み、自分が知っている上限を超えていたら、そこで読み込みを止めます。上位互換はありません(新しい JVM が古い .class を読むのは可能ですが、逆はできません)。

このため、エラーはコンパイル時ではなくクラスをロードした瞬間に出ます。ビルドは緑、テストも(同じ新しい JDK で走っていれば)緑、デプロイして初めて落ちる。手元と CI とサーバで JVM が違うときに、最後の段だけが赤くなるのはこのためです。

依存ライブラリが原因のこともある

自分のコードを 8 向けにビルドしていても、依存している jar が新しい JDK でビルドされていれば同じことが起きます。この場合、エラーに出るクラス名が自分のパッケージではなく、ライブラリのクラス名になります。

java.lang.UnsupportedClassVersionError: com/example/lib/Foo has been compiled by a more recent
version of the Java Runtime (class file version 61.0) ...

クラス名が自分のものでないなら、上げるべきは自分のビルド設定ではなく、そのライブラリの版(新しい JDK を要求しない版へ下げる)か、実行 JVM のほうです。ライブラリは「Java 17 以上が必要」と宣言していることが多いので、リリースノートの必要 JDK 版を確認してください。

切り分け

  • Unsupported class file major version 61 という短い文言で、Gradle から出る別の機構です。 落ちているのはあなたのアプリではなく Gradle 自身で、しかも JVM のクラスロードではなく、Gradle がビルドスクリプトを解析する段階で使うバイトコード読み取りが失敗しています。直し方も違い(Gradle を上げる)、sourceCompatibility を下げても変わりません。Unsupported class file major version 65 の直し方(Gradle が新しい JDK を読めない) を参照してください。
  • class file version の数字が 61 でなく 65 → ビルドしたのは Java 21 なので、上の表で読み替えてください。原因も対処も変わりません。
  • エラーが NoSuchMethodError → 別件です。バイトコードは読めているので、クラスファイル版の不一致ではありません。--release を使わず -source 8 -target 8 でビルドすると、クラスファイルの版は 8 なのに中身が新しい JDK の API を呼ぶ、という形になります(-target だけを指定してもコンパイル自体が通りません)。NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer; の直し方 を参照してください。
  • エラーが NoClassDefFoundError: javax/xml/bind/... → これも別件で、Java 11 が JDK からパッケージごと削除したためです。NoClassDefFoundError: javax/xml/bind/JAXBException の直し方 を参照してください。

検証環境

  • eclipse-temurin:17-jdk、ネットワーク有り。同じイメージに openjdk-8-jre-headless を入れ、ビルド用の JDK 17 と実行用の JVM 8 を同居させています
  • 再現:javac --release 17 でビルドしたクラスを Java 8 の JVM で実行し、class file version 61.0 / up to 52.0 が出て終了コード 1
  • 修正:同じソースを javac --release 8 でビルドし直し、同じ Java 8 の JVM で終了コード 0
  • 対応表(8=52 / 11=55 / 17=61 / 21=65)と「Java 11 で実行すると up to 55.0 になる」「Java 17 で実行すると通る」は、いずれも個別に実行して確認しました

Maven / Gradle の設定と Docker のマルチステージ構成は、機構が同じ(クラスファイル版と JVM の対応)ことを根拠にした対処であり、ビルドツール経由での再現は通していません。

検証(machine-verified)

この修正は eclipse-temurin:17-jdk のバージョン固定コンテナ内で再現し、修正後に has been compiled by a more recent version of the Java Runtime (class file version 61.0) のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs jvm/unsupported-class-version
● reproduce has been compiled by a more recent version of the Java Runtime (class file version 61.0) present ✓
● apply fix exit 0
● re-run has been compiled by a more recent version of the Java Runtime (class file version 61.0) gone ✓
PASS verified · eclipse-temurin:17-jdk · signature gone