バージョンの問題
Javaの開発を行う上で、Javaのバージョンを気にすることは重要です。これは単にバージョンによって使える構文が異なる、といった文法上の違いだけではなく、正しくコンパイルを行ってJavaを実行可能な状態にし、そこからプログラムを正常に実行するためにも、Javaのバージョンを意識しておくことは重要だということです。
個人で開発を行う際は、あまり深く考えずとも単に最新の物をダウンロードしてローカル環境を構築すれば、その時点で最新のバージョンに統一されるためあまり問題はないかもしれません。しかし特にチームで開発を行う場合、各メンバが個人の異なる環境で、異なるタイミングで開発環境を整えることになるため、予めバージョンを揃えておかないと、後で全員の資材を一つにした時に動かないといった問題が起こりかねません。
自分が開発に参加する際、バージョンを指定されることもあると思いますが、そんな時に起こる次のような問題を解消できるよう、このページでは、JRE・コンパイラ・クラスファイルのバージョンについて整理していきます。
- なぜバージョンを合わせる必要があるのか、バージョンを揃えないとどうなるのかよく理解していない。
- どのようにバージョンを確認し、設定すれば良いのか知りたい。
- 基本的な部分を理解して、バージョンに関するエラーが出ても落ち着いて対応できるようにしたい。
- バージョンを合わせて設定したつもりが出来ていなかった。(JREの設定は出来ていた一方で、コンパイラの設定が出来ておらず異なるバージョンのクラスファイルが作成されていた等)
- バージョンを合わせるためにどこを確認すれば良いか、何のバージョンを揃えれば良いのか分からない。
- エラーメッセージにJavaとは異なるバージョン情報(50.0~60.0台)が出力されて対応方法が分からなくなってしまった。
Javaのバージョン
Javaのバージョンと一言で言っても、いくつか指すものがあります。ここでは以下の3つに分けて整理していきます。
- JRE(実行環境)のバージョン
- =
java
コマンドのバージョン $ java --version
で確認する。
- =
- コンパイラのバージョン
- =
javac
コマンドのバージョン $ javac --version
で確認する。
- =
- クラスファイルのバージョン
- =
javac
コマンドで作成した.class
ファイルのバージョン $ javap -v <classes>
で確認する。
- =
多くの場合、単にJavaのバージョンと言えば java
コマンドすなわちJREのバージョンを指すことかと思われます。しかし実際に開発を行う上では、正常にJavaを実行するために、JREだけでなく、コンパイラのバージョンを正しく設定し、作成されるクラスファイルのバージョンが適切になるように気を付ける必要があります。
またこれらに加え、便宜的に、特定のJavaのバージョンの構文が記述された .java
ファイルを、以降「ソースファイルのバージョン」として説明していきます。例えば、Java15で追加された Text Blocks が記述されている .java
ファイルを、“Java15のソースファイル” と呼称するといった具合です。
その上でここまでに挙げた、それぞれがバージョンを持つ各要素は、以下のように関係し合っています。
- コンパイル
- コンパイラと同じ又は下のバージョンのソースファイルをコンパイルできる。
- コンパイラと同じ又は下のバージョンのクラスファイルへコンパイルできる。
- Javaの実行
- JRE(実行環境)と同じ又は下のバージョンのクラスファイルを実行できる。

ここからは、上記の二つ(コンパイル → Javaの実行)について順を追って、先に挙げた三つのバージョン(コンパイラ・クラスファイル・JRE)を詳細に確認していきます。
java
・javac
コマンドの設定方法はこちらに記載しています。コンパイラのバージョン
まずはソースファイルをコンパイルするところを考えます。この段階でのコンパイラのバージョンに関する注意点は、javac
コマンドによるコンパイルでは、コンパイラと同じかそれ以下のバージョンのソースファイルのみをコンパイルすることが可能、ということです。
例えば、Java8, Java11, Java17 の三つのソースファイルに対し、Java11, Java17 の二つのコンパイラを使用する場合、コンパイル可否は次のようになります。
コンパイル可否 | ソースファイルのバージョン | コンパイラのバージョン |
---|---|---|
○ 可能 | Java8 | Java11 |
○ 可能 | Java8 | Java17 |
○ 可能 | Java11 | Java11 |
○ 可能 | Java11 | Java17 |
× 不可 | Java17 | Java11 |
○ 可能 | Java17 | Java17 |
具体的な実行例を確認してみます。まずは以下のような Main.java
を用意します。
import java.util.stream.Stream;
import static java.util.stream.Collectors.joining;
public class Main {
public static void main(String[] args) {
// Java8 (Stream)
String greeting8 = Stream.of("Hello", "World")
.collect(joining(" "));
System.out.println("Java8 : ".concat(greeting8));
// Java11 (var)
var greeting11 = Stream.of("Hello", "World")
.collect(joining(" "));
System.out.println("Java11: ".concat(greeting11));
// Java17 (String#transform)
var greeting17 = Stream.of("Hello", "World")
.collect(joining(" "));
System.out.println(greeting17.transform("Java17: "::concat));
}
}
この main
メソッドには、以下の通り Java8, Java11, Java17 の三種類の構文で同じ処理が三回記述されています。
// Java8 (Stream)
の行- Java8で追加された Stream API を使用
// Java11 (var)
の行- Java10で追加された ローカル変数の型推論
var
を使用
- Java10で追加された ローカル変数の型推論
// Java17 (String#transform)
の行- Java12で追加された String#transform メソッドを使用
まずこのソースファイルは、Java17のコンパイラを使用してコンパイルすることが可能です。この時、同じバージョンだけでなく、それより古いもの(例えばJava11やJava8の書き方の部分)も問題なくコンパイルできていることが分かります。
# コンパイラ(javacコマンド)のバージョンは 17
$ javac --version
javac 17.0.13
# コンパイル実行
$ javac Main.java
# 正常にJavaを実行できる
$ java Main
Java8 : Hello World
Java11: Hello World
Java17: Hello World
しかしJava11のコンパイラを使用すると、Java8とJava11の部分はコンパイルできますが、Java17の部分でコンパイルエラーが発生してしまいます。
# コンパイラ(javacコマンド)のバージョンは 11
$ javac --version
javac 11.0.25
# Stringのtransformメソッド部分でコンパイルエラー
# Java12で追加されたメソッドのためJava11のコンパイラではコンパイルできない
$ javac Main.java
Main.java:20: エラー: シンボルを見つけられません
System.out.println(greeting17.transform("Java17: "::concat));
^
シンボル: メソッド transform("Java17: "::concat)
場所: タイプStringの変数 greeting17
エラー1個
同様にJava8のコンパイラでは、今度はさらに手前のJava11の部分でコンパイルエラーが発生するようになります。
# コンパイラ(javacコマンド)のバージョンは 8
$ javac -version
javac 1.8.0_431
# 型推論のvar部分でコンパイルエラー
# Java10で追加された機能のためJava8のコンパイラではコンパイルできない
$ javac Main.java
Main.java:13: エラー: シンボルを見つけられません
var greeting11 = Stream.of("Hello", "World")
^
シンボル: クラス var
場所: クラス Main
Main.java:18: エラー: シンボルを見つけられません
var greeting17 = Stream.of("Hello", "World")
^
シンボル: クラス var
場所: クラス Main
エラー2個
このように、ソースファイルのバージョン(記載されたJavaの構文)によって、コンパイル可能なコンパイラのバージョンが限定されます。
クラスファイルのバージョン
コンパイラのバージョンは javac
の --version
オプションで確認することができました。しかし見落としがちですが、コンパイラのバージョンとは別に、それによって作成されたクラスファイルにもそれぞれバージョンが存在しています。
クラスファイルのバージョンを確認するには javap
コマンドを使用します。これは javac
コマンド同様、JDKに同梱されているツールになります。( 【Java】JDK と JRE について )
javap
コマンドは逆アセンブラであり、逆コンパイラではありません。そのためクラスファイルから元のソースファイルの情報を得ることはできませんが、今回のようにクラスファイルのバージョンを確認したいという場合には便利です。
クラスファイルのバージョンを確認するには -v
オプションを使用します。先程の例で使用したJava17でコンパイルしたクラスファイルを確認すると、次のような出力を得られます。
# コンパイラ(javacコマンド)のバージョンは 17
$ javac --version
javac 17.0.13
# コンパイル実行
$ javac Main.java
# 逆アセンブラでクラスファイルのバージョンを確認
$ javap -v Main
Classfile /C:/Users/aaa/bbb/ccc/Main.class
Last modified yyyy/mm/dd; size 1610 bytes
SHA-256 checksum xxxxx
Compiled from "Main.java"
public class Main
minor version: 0
major version: 61
・・・略・・・
minor version: 0
major version: 61
メジャーバージョンとマイナーバージョンが別々に出力されていますが、このクラスファイルのバージョンは 61.0 ということが分かります。一見Javaのバージョンとは異なる数字で分かりづらいですが、以下の対応表から、Java17であることを確認できます。(基本的には「Javaのバージョン + 44」と考えれば良さそうです。)
Java SE Released Major Supported majors 1.0.2 May 1996 45 45 1.1 February 1997 45 45 1.2 December 1998 46 45 .. 46 1.3 May 2000 47 45 .. 47 1.4 February 2002 48 45 .. 48 5.0 September 2004 49 45 .. 49 6 December 2006 50 45 .. 50 7 July 2011 51 45 .. 51 8 March 2014 52 45 .. 52 9 September 2017 53 45 .. 53 10 March 2018 54 45 .. 54 11 September 2018 55 45 .. 55 12 March 2019 56 45 .. 56 13 September 2019 57 45 .. 57 14 March 2020 58 45 .. 58 15 September 2020 59 45 .. 59 16 March 2021 60 45 .. 60 17 September 2021 61 45 .. 61 18 March 2022 62 45 .. 62 19 September 2022 63 45 .. 63 20 March 2023 64 45 .. 64 21 September 2023 65 45 .. 65 22 March 2024 66 45 .. 66 23 September 2024 67 45 .. 67
以上のことから、デフォルトではコンパイラと同じバージョンのクラスファイルが作成されていることが分かります。
デフォルトは同じバージョンですが、それより下のバージョンであれば、javac
によるコンパイル時に --release
オプションを使用することで、指定したバージョンのクラスファイルを作成することができます。
例えば --release
オプションを使用してJava12を指定すると、次のように対応する 56.0 のクラスファイルを作成することができます。
# コンパイラ(javacコマンド)のバージョンは 17
$ javac --version
javac 17.0.13
# Java12を指定してコンパイル実行
$ javac --release 12 Main.java
# 逆アセンブラでクラスファイルのバージョンを確認
$ javap -v Main
Classfile /C:/Users/aaa/bbb/ccc/Main.clas
Last modified yyyy/mm/dd; size 1610 bytes
SHA-256 checksum xxxxx
Compiled from "Main.java"
public class Main
minor version: 0
major version: 56
・・・略・・・
minor version: 0
major version: 56
注意点として、コンパイラのバージョンより下であっても、ソースファイルのバージョンより下のバージョンのクラスファイルは作成できないということが挙げられます。これをしようとするとコンパイルエラーとなってしまいます。
例えば次のように、Java10の構文が使用されたソースファイルは、たとえJava17のコンパイラであっても、Java8のクラスファイル(52.0)へコンパイルすることはできません。
# コンパイラ(javacコマンド)のバージョンは 17
$ javac --version
javac 17.0.13
# Java8を指定してコンパイル実行
$ javac --release 8 Main.java
Main.java:13: 警告:リリース10から、'var'は制限された型名であり、型の宣言での使用、または配列の要素タイプとしての使用はできません
var greeting11 = Stream.of("Hello", "World")
^
Main.java:18: 警告:リリース10から、'var'は制限された型名であり、型の宣言での使用、または配列の要素タイプとしての使用はできません
var greeting17 = Stream.of("Hello", "World")
^
Main.java:13: エラー: シンボルを見つけられません
var greeting11 = Stream.of("Hello", "World")
^
シンボル: クラス var
場所: クラス Main
Main.java:18: エラー: シンボルを見つけられません
var greeting17 = Stream.of("Hello", "World")
^
シンボル: クラス var
場所: クラス Main
エラー2個
警告2個
JREのバージョン
ここまで、コンパイラのバージョンとクラスファイルのバージョンをそれぞれ確認してきましたが、java
コマンドによるプログラムの実行は、クラスファイルのバージョンが同じかそれより下であれば可能となります。
例えば次のように、Java17のコンパイラによるコンパイルでは、デフォルトで61.0(Java17)のクラスファイルが作成されるため、同じJava17のJRE(java
コマンド)でJavaプログラムを実行することができます。
# コンパイラ(javacコマンド)のバージョンは 17
$ javac --version
javac 17.0.13
# コンパイル実行
$ javac Main.java
# クラスファイルのバージョンは 61.0(Java17)
$ javap -v Main
Classfile /C:/Users/aaa/bbb/ccc/Main.class
Last modified yyyy/mm/dd; size 1610 bytes
SHA-256 checksum xxxxx
Compiled from "Main.java"
public class Main
minor version: 0
major version: 61
・・・略・・・
# JRE(javaコマンド)のバージョンは 17
$ java --version
java 17.0.13 2024-10-15 LTS
Java(TM) SE Runtime Environment (build 17.0.13+10-LTS-268)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.13+10-LTS-268, mixed mode, sharing)
# プログラム実行
$ java Main
Java8 : Hello World
Java11: Hello World
Java17: Hello World
従って次の例のように、Java23のコンパイラではデフォルトで67.0(Java23)のクラスファイルが作成されるため、Java17のJREを使用してこれを実行しようとするとエラーが発生してしまいます。
# コンパイラ(javacコマンド)のバージョンは 23
$ javac --version
javac 23.0.1
# コンパイル実行
$ javac Main.java
# クラスファイルのバージョンは 67.0(Java23)
$ javap -v Main
Classfile /C:/Users/aaa/bbb/ccc/Main.class
Last modified yyyy/mm/dd; size 1610 bytes
SHA-256 checksum xxxxx
Compiled from "Main.java"
public class Main
minor version: 0
major version: 67
・・・略・・・
# JRE(javaコマンド)のバージョンは 17
$ java --version
java 17.0.13 2024-10-15 LTS
Java(TM) SE Runtime Environment (build 17.0.13+10-LTS-268)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.13+10-LTS-268, mixed mode, sharing)
# プログラム実行エラー
$ java Main
エラー: メイン・クラスMainのロード中にLinkageErrorが発生しました
java.lang.UnsupportedClassVersionError: Main has been compiled by a more recent version of the Java Runtime (class file version 67.0), this version of the Java Runtime only recognizes class file versions up to 61.0
エラー: メイン・クラスMainのロード中にLinkageErrorが発生しました
java.lang.UnsupportedClassVersionError: Main has been compiled by a more recent version of the Java Runtime (class file version 67.0), this version of the Java Runtime only recognizes class file versions up to 61.0
Mainは、より新しいバージョンのJavaランタイム(クラスファイルバージョン 67.0)でコンパイルされています。このバージョンのJavaランタイムは、クラスファイルバージョン 61.0 までしか認識しません。
気を付ける必要があるのは、コンパイラのバージョンではなく、それによって作成されたクラスファイルのバージョンであるという点です。仮にコンパイラのバージョンがJREより上であっても、コンパイル時にオプションを指定して、JREよりも下のバージョンのクラスファイルを作成していれば、問題なくJavaプログラムを実行することが可能となります。
# コンパイラ(javacコマンド)のバージョンは 23
$ javac --version
javac 23.0.1
# コンパイル実行(Java17を指定)
$ javac --release 17 Main.java
# クラスファイルのバージョンは 61.0(Java17)
$ javap -v Main
Classfile /C:/Users/aaa/bbb/ccc/Main.class
Last modified yyyy/mm/dd; size 1610 bytes
SHA-256 checksum xxxxx
Compiled from "Main.java"
public class Main
minor version: 0
major version: 61
・・・略・・・
# JRE(javaコマンド)のバージョンは 17
$ java --version
java 17.0.13 2024-10-15 LTS
Java(TM) SE Runtime Environment (build 17.0.13+10-LTS-268)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.13+10-LTS-268, mixed mode, sharing)
# プログラム実行
$ java Main
Java8 : Hello World
Java11: Hello World
Java17: Hello World
# コンパイラ(javacコマンド)のバージョンは 23
$ javac --version
javac 23.0.1
# コンパイル実行(Java12を指定)
$ javac --release 12 Main.java
# クラスファイルのバージョンは 56.0(Java12)
$ javap -v Main
Classfile /C:/Users/aaa/bbb/ccc/Main.class
Last modified yyyy/mm/dd; size 1610 bytes
SHA-256 checksum xxxxx
Compiled from "Main.java"
public class Main
minor version: 0
major version: 56
・・・略・・・
# JRE(javaコマンド)のバージョンは 17
$ java --version
java 17.0.13 2024-10-15 LTS
Java(TM) SE Runtime Environment (build 17.0.13+10-LTS-268)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.13+10-LTS-268, mixed mode, sharing)
# プログラム実行
$ java Main
Java8 : Hello World
Java11: Hello World
Java17: Hello World
Eclipseの場合
最後にEclipseを利用している場合のバージョン設定について触れておきます。ここまでの説明では、分かりやすいよう主にコマンドを使用した例を挙げてきました。しかし、いざこの内容を活用してエラー解消を行おうとした時、例えばEclipseで開発を行っていると、どこからバージョンの設定を行えば良いのか分からないとなってしまうことがあります。
このように、起きている問題とその解消方法に目途は立っているものの、コマンドではなくIDEから同じ設定を行うには、どうすれば良いか分からないということが往々にしてあります。基礎的な部分が分かっていることと、IDE特有の使用方法に精通していることは異なり、また違った知識が必要になってくるためです。
EclipseでJavaやコンパイラのバージョン設定を行うためには、それぞれ設定画面から操作を行い、バージョンを変更する必要があります。具体的な手順については以下で整理しています。