Java 16またはJava 17にアップグレードする理由と方法

キーポイント

原文(投稿日:2021/09/08)へのリンク

2021年4月27日のInfoQ Liveで、「Upgrade to Java 16 or 17 (Java 16 または 17 へのアップグレード)」を発表し、Java 16 または Java 17 へのアップグレードを検討する理由 (リリース後) について、アップグレードの方法に関する実践的なアドバイスを含めて説明しました。

このセッションは、私の個人的な GitHub リポジトリである JavaUpgrades に基づいていました。このリポジトリには、Java 16 または Java 17 にアップグレードする際の一般的な課題と例外に関するドキュメントと例が含まれています。あなたのアプリケーションに、同様に含まれている具体的なソリューションが使用できます。例は Docker で実行され、Maven でビルドされますが、もちろん、Gradle ビルドをセットアップすることもできます。

この記事は、私の InfoQ セッションの記録とともに、Java 16 または Java 17 へのアップグレードプロセスを容易にすることを目的としています。最も一般的なアップグレードタスクについて説明しているので、それらを容易に解決し、アプリケーション固有の課題に集中できます。

なぜアップグレードするのか?

すべての新しい Java リリース、特にメジャーリリースは、セキュリティの脆弱性が解決され、パフォーマンスが改善され、新しい機能が追加されるなどの改善が含まれています。Java を最新の状態に保つことは、正常なアプリケーションを維持するのに役立ちます。そうすることで、組織は既存の従業員を維持し、新しいテクノロジーに取り組むことに開発者が一般的に熱心になるため、新しい従業員を引き付ける可能性もあります。

アップグレードは時々困難とみなされる

新しいバージョンの Java にアップグレードするには、かなりの作業が必要になる可能性があると認識されています。これは、コードベースに必要な変更を加え、アプリケーションをビルドして実行するすべてのサーバに最新バージョンの Java をインストールするためです。幸いなことに、一部の企業は Docker を使用しており、チームがそれらのアイテムを彼ら自身でアップグレードできるようになっています。

多くの開発者は、Java 9 モジュールシステム (別名 Jigsaw) を大きな課題とみなしています。ただし、Java 9 では、モジュールシステムを明示的に使用する必要はありませんでした。実際、Java 9 以降で実行されているほとんどのアプリケーションは、コードベースに Java モジュールを構成していません。

アップグレードに必要な作業量を見積もるのは難しい場合があります。依存関係の数や現在の依存関係など、さまざまな要素によって異なります。たとえば、Spring Boot を使用している場合、Spring Boot をアップグレードすると、ほとんどのアップグレードの問題がすでに解決されている可能性があります。残念ながら、不確実性のため、ほとんどの開発者はアップグレードに数日、数週間、あるいは数ヶ月もかかると見積もっています。これにより、組織または管理者は、コスト、時間、またはその他の優先順位のためにアップグレードを延期する可能性があります。Java 8 アプリケーションを Java 11 にアップグレードするのに数週間から数か月かかる見積もりを見てきました、しかし、私はほんの数日で同様のアップグレードをやりとげました。これは以前の経験によることもありますが、アップグレードプロセスを開始するだけの問題でもあります。Java をアップグレードして何が起こるかを確認することは、金曜日の午後のタスクが理想的です。最近、Java 11 アプリケーションを Java 16 に移行しましたが、必要なタスクは Lombok 1つの依存関係をアップグレードすることだけでした。

アップグレードは難しく、時間を見積もることは不可能に思えるかもしれませんが、実際のアップグレードプロセスにはそれほど長くはかからないことがよくあります。多くのアプリケーションをアップグレードしているときに同じ問題が発生しました。チームが繰り返し発生する問題を迅速に解決して、アプリケーション固有の課題に集中できるように支援したいと思います。

Java リリースのリズム

過去には、Java の新しいバージョンは数年ごとにリリースされていました。ただし、Java 9 リリース以降、リリースのリズムは6か月ごとに変更され、3年ごとに新しい長期サポート (LTS) バージョンがリリースされています。ほとんどの非 LTS バージョンは、次のバージョンがリリースされる頃までの約6か月間、マイナーアップデートでサポートされます。一方、LTS バージョンは、少なくとも次の LTS リリースまで、何年にもわたってマイナーアップデートを受け取ります。OpenJDK ベンダー (Adoptium、Azul、Corretto など) によっては実際はサポートがはるかに長く利用できる場合があります。たとえば、Azulは、LTS 以外のバージョンでより長いサポートを提供します。

「常に最新バージョンにアップグレードすべきだろうか、それともLTS リリースのままにすべきだろうか?」と自問するかもしれません。アプリケーションを LTS バージョンに維持することは、さまざまな改善、特にセキュリティに関連する改善により、マイナーアップグレードを簡単に利用できることを意味します。一方、最新の非 LTS バージョンを使用する場合は、6か月ごとに新しい非 LTS バージョンにアップグレードしなければなりません。そうしないとマイナーアップグレードは利用できなくなります。

ただし、6か月ごとのアップグレードは、アプリケーションをアップグレードする前にフレームワークがアップグレードされるのを待たなければならない場合があるため、困難な場合があります。ただし、非 LTS バージョンのマイナーアップグレードがリリースされなくなるまでのようには、あまり長く待つ必要はありません。私たちの会社では、6か月ごとの短い時間枠でアップグレードする時間があるとは思えないため、今のところ LTS バージョンを使用することにしました。ただし、チームが本当に望んでいる場合、または非 LTS バージョンに興味深い新しい Java 機能がリリースされている場合は、まだ議論の余地があります。その場合は、決定を修正する可能性があります。

何をアップグレードするのか?

一般に、アプリケーションは依存関係とパッケージ化されて JDK で実行される独自のコードで構成されます。JDK で何かが変更された場合は、依存関係、独自のコード、またはその両方を変更する必要があります。ほとんどの場合、これは JDK から機能が削除されたときに発生します。依存関係で削除された JDK 機能を使用している場合は、新しいバージョンの依存関係がリリースされるまで辛抱強く待つことが役立ちます。

複数の JDK

アプリケーションをアップグレードするときは、実際のアップグレードのための最新バージョンとアプリケーションを維持するための古いバージョンなど、2つの異なるバージョンの JDK を使用することをお勧めします。アプリケーション開発に使用される JDK の現在のバージョンは、JAVA_HOME 環境変数または SDKMAN! や JDKMon などのパッケージ管理ユーティリティで指定できます。

GitHub リポジトリの例では、Docker を使用してさまざまな JDK バージョンを使用して特定の機能がどのように機能するか、または機能しないかを示しています。これにより、JDK の複数のバージョンをインストールしなくてもそれらを試すことができます。残念ながら、Docker コンテナのフィードバックループは少し長いです。まず、イメージをビルドして実行する必要があります。したがって、一般的には IDE 内から可能な限りアップグレードすることをお勧めします。ただし Docker コンテナに個人設定を行わずにクリーンな環境で、いくつかのことを試したり、アプリケーションをビルドすることは良いアイデアだと思います。

Java 16またはJava 17にアップグレードする理由と方法

これを実証するために、以下に示したコンテンツを含む標準Dockerfile ファイルを作成してみましょう。この例では、Maven JDK 17 イメージを使用し、アプリケーションソースをイメージ内にコピーします。RUN コマンドは続けてすべてのテストを実行し、エラーが発生しても失敗しません。

FROM maven:3.8.1-openjdk-17-slimADD . /yourprojectWORKDIR /yourprojectRUN mvn test --fail-at-end

上のイメージをビルドするには、docker build コマンドを呼び出して、タグ (または名前) を示す-t フラグとコンテキスト (この場合は現在のディレクトリ) を構成する. を追加します。

docker build -t javaupgrade .

準備

ほとんどの開発者は、まずローカル環境を、次にビルドサーバを、そして次にさまざまなデプロイ環境をアップグレードします。ただし、時には特定のプロジェクトのすべての構成をセットアップせず、単純に新しい Java バージョンを使用してビルドサーバ上でビルドを作成し、問題が発生する可能性があることを確認することがあります。

Java 8 から 17 に一度にアップグレードすることが可能です。ただし、問題が発生した場合は、それらの Java バージョン間のどの新機能が問題の原因であるかを特定するのが難しい場合があります。たとえば、Java 8 から Java 11 に少しずつアップグレードすると、問題を特定しやすくなります。また、検索クエリに問題の原因となった Java バージョンを追加できる場合は、問題の検索にも役立ちます。

まず、古いバージョンの Java の依存関係をアップグレードすることをお勧めします。そうすれば Java を同時にアップグレードせずに依存関係を機能させることに集中できます。残念ながら、一部の依存関係は新しいバージョンの Java が必要になるかもしれず、これが常に可能であるとは限りません。その場合は、Java と依存関係の両方を一度にアップグレードする以外に選択肢はありません。

依存関係の新しいバージョンを表示する、利用可能な Maven および Gradle プラグインがいくつかあります。mvn versions:display-dependency-updates コマンドは、Maven Versions Plugin を呼び出します。プラグインは、利用可能な新しいバージョンを持つそれぞれの依存関係を表示します:

[INFO] --- versions-maven-plugin:2.8.1:display-dependency-updates (default-cli) @ mockito_broken ---[INFO] The following dependencies in Dependencies have newer versions:[INFO]org.junit.jupiter:junit-jupiter .................... 5.7.2 -> 5.8.0-M1[INFO]org.mockito:mockito-junit-jupiter ................... 3.11.0 -> 3.11.2

gradle dependencyUpdates -Drevision=release コマンドは、build.gradle ファイルでプラグインを構成した後、Gradle Versions Plugin を呼び出します:

plugins { id "com.github.ben-manes.versions" version "$version" }

依存関係をアップグレードしたら、Java をアップグレードします。もちろん、新しいバージョンの Java で動作するようにコードを変更して、IDE で最適に機能する、最新の Java バージョンをサポートしていることを確認してください。その後、ビルドツールを最新バージョンにアップグレードし Java バージョンを構成します:

 org.apache.maven.plugins maven-compiler-plugin 3.8.117
plugins { java {toolchain {languageVersion = JavaLanguageVersion.of(16)} }}
compile 'org.apache.maven.plugins:maven-compiler-plugin:3.8.1'

Maven と Gradle プラグインも最新バージョンに更新することを忘れないでください。

JDK から削除された機能

JDK から削除される要素がある可能性は常にあります。これらにはメソッド、証明書、ガベージコレクションアルゴリズム、JVM オプション、さらには完全なツールが含まれます。ただし、ほとんどの場合、これらのアイテムは最初は非推奨であるか、削除マークが付けられています。たとえば、JAXB は元々 Java 9 で非推奨になり、最終的に Java 11 で削除されました。非推奨になった機能に関連する問題をすでに解決している場合は、実際に削除されても心配はいりません。

Java Version Almanac または Foojay Almanac をリファレンスとして使用して、Java のさまざまなバージョンを比較し、追加、非推奨、または削除されたアイテムを確認できます。Java拡張プロポーザル (JEP) 形式でのハイレベルの変更は、OpenJDK Web サイトで入手できます。それぞれの Java リリースの詳細については、Oracle が発行するリリースノートを参照してください。

Java 11

さまざまな機能が Java 11 で削除されました。最初の機能は JavaFX であり、仕様の一部ではなくなり、OpenJDK にバンドルされていません。ただし、仕様に含まれている以上のものを含む JDK ビルドを提供しているベンダーがあります。たとえば、ojdkbuild と Liberica JDK の完全な JDK には、どちらも OpenJFX が含まれています。または、Gluon が提供する JavaFX ビルドを使用するか、OpenJFX の依存関係をアプリケーションに追加することもできます。

JDK 11 より前は、いくつかのフォントが JDK に含まれていました。たとえば、Apache POI は Word および Excel ドキュメントにこれらのフォントを使用できました。ただし、JDK 11 以降、これらのフォントは使用できなくなりました。オペレーティングシステムがそれらも提供しない場合は、奇妙なエラーが発生する可能性があります。解決策は、オペレーティングシステムにフォントをインストールすることです。アプリケーションで使用されるフォントによっては、さらにいくつかのパッケージをインストールする必要がある場合があります:

apt install fontconfigOptional: libfreetype6 fontconfig fonts-dejavu

監視およびプロファイリングアプリケーションである Java Mission Control (JMC) は、最小限のオーバーヘッドでプロダクションを含むあらゆる環境でアプリケーションをプロファイリングできます。まだ試したことがない方には、ぜひお勧めします。これは JDK 自体のパーツではなくなりましたが、AdoptOpenJDK と Oracle による JDK Mission Control という新しい名前で別のダウンロードで提供されています。

Java 11 での最大の変更点は、Java EE にすでに含まれているため冗長と見なされていた4つの Web サービス API (JAX-WS、JAXB、JAF、Common Annotations) などの Java EE と CORBA モジュールの削除でした。Oracle は、Java EE をオープンソースにすることを目的として、2017年のリリース直後に Java EE 8 を Eclipse Foundation に寄贈しました。Oracle のブランドポリシーにより、Java EE の名前を Jakarta EE に変更し、javax からjakarta に名前空間を移行する必要がありました。したがって、JAXB などの依存関係を使用する場合は、必ず新しい Jakarta EE アーティファクトを使用してください。たとえば、Java EE 8 バージョンでの名前が javax.xml.bind:jaxb-api の JAXB アーティファクトの先の開発は2018年に終了しました。JAXB のJakarta EE バージョンの開発は、新しいアーティファクト名jakarta.xml.bind:jakarta.xml.bind-api で続行されます。アプリケーション内のすべてのインポートを新しいjakarta 名前空間に変更することも確認してください。JAXB の場合、たとえば、javax.xml.bind.*jakarta.xml.bind.* に変更し、関連する依存関係を追加します。

下の画像は、この変更によって影響を受けるモジュールを左の列に示しています。右側の2つの列は、依存関係として使用できるgroupIdartifactId を示しています。JAXB と JAX-WS の両方に2つの依存関係が必要であることに注意してください。1つは API 用で、もう1つは実装用です。CORBA には公式の Jakarta の代替はありませんが、Glassfish には引き続き使用できるアーティファクトを提供します。

Java 15

Nashorn JavaScript Engine は Java 15 で削除されましたが、依存関係を追加することで引き続き使用できます:

 org.openjdk.nashorn nashorn-core 15.2

Java 16

このリリースでは、JDK 開発者はいくつかの JDK 内部をカプセル化しました。それらは、あなたや他の誰かが JDK の低レベル API の使用をもはや望んでいません。これは主に Lombok のようなツールに影響を与えました。幸い、Lombok の場合は問題を解決する新しいバージョンが数週間の内にリリースされました。

JDK 内部をまだ使用しているコードまたは依存関係がある場合、それらを再度開くことができます。ドアから錠を外すような、かなり「ダーティ」と感じるかもしれません。JDK のハイレベル API を使用して問題を解決してみてください。それが不可能な場合に、この回避策を Maven で利用できます:

 org.apache.maven.plugins maven-compiler-plugin 3.8.1true-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED-J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED-J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED-J--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED-J--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED-J--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED

私は、Maven ツールチェーンを使用してpom.xml ファイルで JDK を指定することにより、JDK を切り替えようとしました。残念ながら、古いバージョンの Lombok で Java 16 を使用してアプリケーションを実行するとエラーが発生しました:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project broken: Compilation failure -> [Help 1]

これが表示されたすべての情報でした。あなたにとってはわかりませんが、私にとってはあまり役に立たなかったので、この問題を提出しました。問題が修正されてから、Maven ツールチェーンを使用してバージョンを切り替えることができます。その後、Java 16 で直接コードを実行したところ、前に示した回避策の一部に言及する、より説明的なエラーが発生しました:

… class lombok.javac.apt.LombokProcessor (in unnamed module @0x21bd20ee) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module …

Java 17

JDK のメンテナは、9月のリリースの内容についてすでに合意しています。ブラウザがアプレットのサポートをかなり前に停止しているため、Applet API は非推奨になります。実験的な AOT および JIT コンパイラも削除されます。実験的なコンパイラの代わりには、GraalVM を使用できます。最大の変更点は、JEP-403: Strongly Encapsulate JDK internals (JDK内部を強力にカプセル化する) です。Java オプション--illegal-access は機能しなくなり、引き続き内部 API にアクセスすると、次の例外がスローされます:

java.lang.reflect.InaccessibleObjectException:Unable to make field private final {type} accessible:module java.base does not "opens {module}" to unnamed module {module}

ほとんどの場合、これは依存関係をアップグレードするか、よりハイレベルの API を使用することで解決できます。それができない場合は、--add-opens 引数を使用して、内部 API へのアクセスを再度許可できます。ただし、これは最後の手段としてのみ使用してください。

一部のツールは Java 17 ではまだ機能しないことを知っておいてください。たとえば、Gradle はプロジェクトをビルドできず、Kotlin はjvmTarget = "17" を使用できません。Mockito などの一部のフレームワークでは、Java 17 でいくつかの小さな問題が発生しました。たとえば、enum フィールドのメソッドがこの特定の問題を引き起こしました。ただし、ほとんどの問題は Java 17 のリリース前またはリリースの少し後には解決されると思います。

アプリケーションのビルド時に、プラグインまたは依存関係のいずれかで「Unsupported class file major version 61」というメッセージが表示される場合があります。クラスファイルのメジャーバージョン 61 は Java 17 で使用され、60 は Java 16 で使用されます。これは基本的にプラグインまたは依存関係がそのバージョンの Java では機能しないことを意味します。ほとんどの場合、最新バージョンにアップグレードすると問題が解決します。

終わりに

すべての課題を解決した後、最終的に Java 17 でアプリケーションを実行できます。すべてのハードワークの後、レコードやパターンマッチングなどのエキサイティングな新しい Java の機能を使用できるようになります。

結論

Java のバージョンと依存関係の古さ、およびセットアップの複雑さによっては、Java のアップグレードが難しい場合があります。このガイドは Java をアップグレードする際の最も一般的な課題の解決に役立ちます。一般に、アップグレードに実際にかかる時間を見積もることは困難です。しかし、一部の開発者が考えるほど難しくないことが多いことを経験しました。ほとんどの場合、Java 11 から Java 17 へのアップグレードは Java 8 から Java 11 へのアップグレードよりも簡単です。ほとんどのアプリケーションでは、ある LTS から次の LTS に移行するのに数時間から数日かかります。ほとんどの時間は、アプリケーションの構築に費やされます。開始して段階的に変更を加えることが重要です。そうすれば、あなた自身、あなたのチーム、そして経営陣がそれに取り組み続けるように動機付けることができます。

アプリケーションのアップグレードをもう始めましたか?

著者について

Johan Janssen氏 は、Sanoma Learningの教育部門のソフトウェアアーキテクトです。主にJavaに関する知識を共有するのが大好きです。Devoxx、Oracle Code One、Devnexusなどのカンファレンスで講演しました。プログラム委員会に参加し、JVMCONを考案し組織化することでカンファレンスを支援しました。JavaOne Rock StarおよびOracle Code One Star賞を受賞しました。デジタルと印刷メディアの両方でさまざまな記事を書きました。ChocolateyでのさまざまなJava JDK/JREパッケージのメンテナで、月に約10万のダウンロードがあります。

タグ: