企業は伝統的に、安全性が完全に証明されるまで、最新バージョンのJavaへのアップグレードには消極的です。2017年9月のJava 9のリリース以降、6か月毎に新たなバージョンがリリースされるようになったことで、この傾向にはますます拍車がかかっています。これにより、Java 9はリリースから2年経っていませんが、最新バージョンはすでにJava 12です。
この変化のペースには、確かに気後れするかも知れません。Java 8からのアップグレードにもその傾向が見られます。アプリケーションの大半はいまだJava 8上で動作していて、Java 12への移行には多くの作業が必要です。この記事では、次のことを見ていきます。
- アップグレードのメリット
- アップグレードに伴う潜在的な問題
- アップグレードのヒント
アップグレードする理由
アップグレード方法の詳細に入る前に、なぜアップグレードしたいのかを十分に検討する必要があります。
機能
開発者である私たちは、最新の言語機能や、言語への更新毎に追加されるAPIに関心があります。Java 8以降、多くの新機能が追加されており、開発手法を変える可能性のある新しいツールもいくつかあります。最新バージョンのJavaを使用して開発することで、自分たちの作業が楽になると開発者が言う、重要な機能をいくつか拾ってみました。
ローカル変数型推論(var
)は、定型コードを減らすのに役立つシンタックスシュガーの好例です。可読性の問題がすべてこれで解決する訳ではありませんが、適切なタイミングで使用すれば、定型コード(どうやってやるか)ではなく、ビジネスロジック(何をやるか)に集中することができます。
コレクション用の便利なファクトリメソッドを使用すれば、リストやマップ、セットなどのコレクションを簡単に生成できます。ファクトリメソッドはまた、変更不可能なコレクションを生成することで、より安全な使用を可能にします。
変更不可能なコレクションへの収集は、Streams用の新しいCollectorオペレーションを使用して、結果を変更不可能なコレクションに格納することを可能にします。
Predicate::notは、述語ラムダやメソッド参照を否定する簡易な方法を提供します。これもまた、定型コードの削減に有効です。
新しいOptionalメソッドには、Optionalを使用する場合に、格好の悪いif文を使用せず、関数スタイルでコーディングするオプションが追加されています。
JShellは、コードを1行ずつ、あるいはスクリプトとして、Javaで実行可能なREPLです。これは新しい機能を試すのに便利です。実運用中のアプリケーションのコードでJavaの新バージョンを採用しなくても、開発マシン上でローカルに使用することが可能になります。
HttpClientはJDKに組み込まれています。ほとんどの人たちは、WebアプリケーションやRESTサービスなどを介して、何らかの形でHTTPを使用しています。この内蔵クライアントは、外部の依存関係の必要性を取り除き、同期と非同期の両方のプログラミングモデルでHTTP 1.1と2をサポートします。
マルチリリースjarファイルは、最新バージョンのJavaを使用するユーザと、古いバージョンの使用を余儀なくされているユーザのニーズを、開発者が同時にサポートするために使用できるツールライブラリのひとつです。
JLinkは、本当に必要なJDKのセクションのみをパッケージ化し、デプロイすることを可能にする、Javaモジュールシステムが実現した素晴らしいツールです。
パフォーマンス
一般論として新しいリリースは、以前のものよりもパフォーマンスが優れています。 "優れている"形はさまざまですが、最近のリリースでは特に、起動時間の改善やメモリ使用量の削減、特定のCPU命令を使用することによる使用CPUサイクルの低減などが見られます。Java 9、10、11、12では、いずれもガベージコレクタに大きな変更が加えられていて、デフォルトのガベージコレクタのG1への変更、G1の改善、3つの新たな実験的ガベージコレクタ(Java 11ではEpsilonとZGC 、Java 12ではShenandoah )などが実施されています。3つは多すぎると思われるかも知れませんが、それぞれのコレクタはさまざまなユースケースに最適化されているので、自身のアプリケーションに最も適したプロファイルを備えた、現代的なガベージコレクタの選択肢が手に入った、ということにもなります。
コスト削減
最近のバージョンのJavaにおける改善は、コストの削減を可能にします。中でも、デプロイするアーティファクトのサイズを縮小するJLinkのようなツール、最近のメモリ使用量の改善は、例えば、クラウドコンピューティングのコスト低減を可能にしています。
新たなことを学ぶ機会を得てスキルを向上させたい、と考える開発者はたくさんいます。最新バージョンを利用することにより、幅広い開発者を引きつけることができるという点も、無視できない潜在的メリットです。最新バージョンのJavaの導入は、開発者の定着と採用に影響し、結果的にはチームの運営コストにも影響するのです。
アップグレードに関する懸念
Java 8以降、言語機能の観点だけでなく、多くのことが変わりました。Oracleは、ライセンスが異なる2つの異なるビルド(本番環境で使用する場合は有償となる"コマーシャルビルド"と、オープンソースの"OpenJDKビルド")のリリースを開始し、それぞれのアップデートモデルとサポートモデルを変更しました。この変更はコミュニティの混乱を招きましたが、結果的には、私たちがそれまで使用してたJDKに加えて、より多くの選択肢が得られることになりました。選択肢は何か、JDKに何を求めるのか、といったことを考える必要があります。
バージョン選択
最新バージョンがJava 12であることを考慮すれば、Java 8からアップグレードするバージョンの選択肢はたくさあると思われるかも知れません。実際の選択は、これよりも少し単純です。現在は、6か月毎にリリースが行われています。新しいリリースがそれぞれ、その前のリリースを置き換えます。例外は、3年毎に長期サポート(LTS)リリースがあることです。これにより、6か月毎に最新のものにアップグレードするか、または3年毎に次のLTSリリースへ、従来型の大規模アップグレードを実行するか、都合のよいアップグレードパスを選択できるようになります。Java 8はLTSでしたが、Oracleは今年1月、コマーシャルユース用の(無償の)アップデート提供を停止しました。Java 11が現在のLTSであり、Java 12がリリースされた今、Oracleからは無償のコマーシャルアップデートは提供されていませんが、最低3年間のアップデートを提供する組織が構築した、多数のJDKビルドがあります。
ここで直面する選択は次のとおりです — すなわち、最新バージョンのJava(12)にアップグレードし、6ヶ月毎のアップグレードに備えるか、最新のLTS(11)にアップグレードして、最長3年間まで次のアップグレードの検討を先延ばしするか。です。補足すれば、大規模な組織はLTSのリリースから次のLTSリリースにジャンプするが、スタートアップ企業は6ヶ月単位でアップデートするものと予想されます。より迅速かつ予測可能なリリースのよいところは。この両号のオプションをサポートしていることです。
ビルド選択
OracleがLTSリリースの無償アップデートを提供していないからといって、実運用環境でLTSを実行して、無償アップグレードを入手することが不可能である、という意味ではありません。OpenJDK(OracleのコマーシャルJDKさえも基盤とする、JDKの参照実装)のビルドを提供する組織やベンダは、Oracle以外にもたくさんあるのです。ここでそのすべてを列挙するよりも、無償OpenJDKビルドの提供者、無償の公開アップデートの提供予定日、有償サポートの詳細をカバーしたこのリストをぜひチェックしてください。
この状況は、以前よりも複雑に思われるかも知れません。考慮すべきファクタがたくさんあるのは事実ですが、しかしこれは、より多くの選択肢を持ったことの副作用なのです。The Java Champoionsでは、ライセンスやサポート、アップデート、選択可能なさまざまなオプションといったトピックについて、より詳細な議論の場を用意しました。トピックの中には、QCon London 2019のQ&Aセッションも含まれています。
Java 9がすべてを駄目にしたのか?
Java 8からのアップグレードを検討する上で、多くの開発者が持つ関心事の中心は、Java 9で導入された大きな変更と、その変更がアプリケーションの動作を損なうかも知れないという不安です。変更点のひとつが、 内部APIのカプセル化です。これによって、以前のJDKでは使用可能だった一部のメソッドに、アクセスすることができなくなりました。このことはtools.jarとrt.jarの削除とともに、Java 9リリース時には憂慮すべき点に思えましたが、今になってみると、アプリケーション開発者よりもライブラリ開発者やフレームワーク開発者、言語開発者にとって問題になっているようです。
アプリケーションが本来すべきでないこと(内部APIや廃止予定のメソッドの使用など)をしていなければ、Java 9以降へのマイグレーションは、思うほど恐ろしいものではありません。コミュニティが直面していた問題の多くは、実際には、私たちが使っているビルドツールやライブラリですでに対処されているのです。
アップグレードのヒント
アップグレードのプロセスはすべて、マイグレーション対象のアプリケーションに固有のものですが、プロセスを容易にするのに役立つ、基本的なグッドプラクティスもいくつかあります。プラクティスは実行順序に沿って概説されています。最初の数ステップについては、アップデートしたJDKを使う必要さえないことが分かるでしょう。
コンパイラの警告に対処する
警告には理由があります。それらを読めば、将来的に廃止されるかも知れない機能について述べられていることが少なくありません。Java 8 JDKを使ってJava 8で開発していて、廃止予定の機能や使用すべきでない機能について警告が表示される場合は、新しいJDKに移行する前に使用を止めて(図1参照)、警告を修正してください。
図1 — JDK 8におけるコンパイラ警告の例
最近のJavaの世界では、非推奨は以前よりもずっと深刻な意味があります。Java 10でもJava 11でもAPIが削除されているのです。
内部APIの使用を確認する
Java 9で行われた変更のひとつは、内部API(おもにsun.misc.*
で始まるパッケージ内のクラス)が使用できないようになったことです。
jdepsというツールがあります。これはJDKの一部で、-jdkinternals
フラグを付けて実行することで、一連のクラスが使用すべきでないものかどうかをチェックできます。例として、プロジェクトの出力ディレクトリ内で、次のコマンドを実行すると、
> $JAVA_HOME\bin\jdeps -jdkinternals .
次のように出力されます。
. -> /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar
com.mechanitis.demo.sense.twitter.connector.TwitterOAuth (.)
-> sun.misc.BASE64Encoder JDK internal API (rt.jar)
-> sun.misc.Unsafe JDK internal API (rt.jar)
警告:JDKの内部APIはサポート対象外で、JDK実装に固有のもので、互換性のない方法で削除または変更される場合があるため、アプリケーションを棄損する可能性があります。
JDK内部APIへの依存を排除するように、コードを修正してください。 JDK内部APIの置き換えに関する最新情報は、次のURLを確認してください — https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
JDK内部API 推奨される置き換え
---------------- ---------------------
sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8
sun.misc.Unsafe See http://openjdk.java.net/jeps/260
このツールは、内部APIを使用するクラスを特定するだけでなく、代わりに何を使用するべきかについての提案も行います。
ビルドツールをアップグレードする
MavenやGradleを使用している場合は、アップグレードが必要です。
Gradle
Gradle 5.0でJava 11のサポートが導入されています。ですから、少なくとも5.0を使うべきです。現在のバージョンは5.3.1で、先月リリースされました。
Maven
最低でも、バージョン3.5.0(現在のバージョンは3.6.0)を実行する必要があります。また、Mavenコンパイラプラグインについては、バージョン3.8以降であることを確認してください。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>11</release> <!-- or 12 -->
</configuration>
</plugin>
依存関係をアップグレードする
Java 9以降へのマイグレーションについて耳にする問題のいくつかは、ライブラリやフレームワークに関係する(従って、それらによって修正される可能性のある)問題です。例えば、フレームワークの多くは、内部的にリフレクションや内部APIを使用しています。アプリケーションが正常に機能し続ける可能性を最大限にするには、すべての依存関係が最新であることを確認する必要があります。多くのライブラリはJava 9以降で動作するようにすでに更新されており、これをさらに確実にするためのコミュニティの取り組みが進められています。
一部の依存関係については、リプレースが必要になる可能性があります。例えば、新しいJavaバージョンすべてで正常に機能するという理由から、多くのライブラリが、コード生成とプロキシをByte Buddyに移行しています。Java 11へのマイグレーションについて調査する場合には、使用している依存関係や、それらが8以降のバージョンのJavaをサポートするように更新されているかどうかについて、十分に理解しておく必要があります。
移動した機能への依存関係を追加する
JDKのコアではないAPIは削除されました。この中には、 Java EEとCorbaモジュール、JavaFXが含まれています 。これらは通常、適切なライブラリを依存関係管理に追加することで、簡単に修正できます。JAXBへの依存関係をGradleあるいはMavenに追加する、といったようにです。JavaFXの場合はもう少し複雑ですが、OpenJFXサイトによい資料があります。
新しいJDKでアプリケーションを実行する
最新バージョンのJavaを使用するために、アプリケーションを再コンパイルする必要はありません。これは、言語開発者が後方互換性の維持に努力している理由のひとつです。継続的インテグレーション環境で、コードを変更することなく、(例えば)新しいJDKを使用してアプリケーションを実行可能な場合もあります。
新しいJDKでコンパイルする
これまで紹介したステップではすべて、アプリケーションを引き続きJava 8でコンパイルすることが可能です。上記以外の手順を実行したときにのみ、Java 11または12でコンパイルすることを検討してください。新しい機能を使用する意思がなければ、以前の言語バージョンでもアプリケーションのコンパイルが可能なのですから、いつでも古いバージョンにロールバックできるようにしておきましょう。例えばMavenでは、
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>8</release>
</configuration>
</plugin>
あるいは、Gradleならば、
sourceCompatibility = JavaVersion.VERSION_1_8
新機能の使用を開始する
すべてがうまくいき、すべてのテストが成功して、パフォーマンス的にも何ら問題なく、運用環境で一定期間安全に実行できた場合にのみ、新たな言語機能の使用を検討するべきです。
また、Java 9リリースはJava Module Systemを導入するためのものでしたが、モジュールをサポートするバージョンに移行したとしても、アプリケーションがそれを使用する必要はまったくありません。ただし、モジュールシステムの採用に興味があるのであれば、InfoQにチュートリアルが用意されています。
まとめ
Java 8以降、多くの変更が行われました — リリースは6か月毎になりました。ライセンス、アップデート、サポートが変更されています。 JDKの入手先も変わっているかも知れません。それに加えて、もちろん、Java 9で実施された大きな変更を含む、新たな言語機能があります。しかし、Java 11が最新のLTSとしてJava 8に取って代わり、主要なライブラリやフレームワーク、ビルドツールが最新バージョンのJavaを採用した現在は、アプリケーションをJava 11あるいは12に移行するよい機会です。
この最初のアップグレードという"山を越えて"しまえば、例えばCIでは、6ヶ月毎にリリースされる最新バージョンのJavaで、少なくともテストを行っておく価値はあります。理想的な状況であれば、6ヶ月のリリーストレインに乗って、6か月毎に次期バージョンのJavaを使用し、新機能が利用可能になればすぐに使用する、ということも可能になります。
著者について
Trisha Gee氏はJava Championです。金融、製造、ソフトウェア、非営利を含むざまざまな業界で、あらゆる規模の企業のためのJavaアプリケーションを開発してきました。氏はJavaのハイパフォーマンスシステムを専門とし、開発者の生産性を高めることに情熱を注いでおり、オープンソース開発にも精通しています。氏は現在、JetBrainsのJava Developer Advocateです。Javaテクノロジの最先端を生きる上で、これが完璧な口実となっています。