2018/07/15

Case Study of Migration to Java SE 11

このエントリーをはてなブックマークに追加

It is almost two months to release Java SE 11. Java SE 11 is the first release of LTS. Terefore, I assume many developers think to skip Java 9 or 10, and use Java 11.

However, it is hard to migrate to Java SE 11. The main reason is Jigsaw. Module system was introduced into Java SE. In addition, some features such as JAXB will remove from Java SE because of module system.

So, I'll show a case to migrate to Java SE 11 using simple sample application.

Sample of Jersey + Grizzly

Jersey is, you may know, RI of JAX-RS. I picked up a sample that was described in Jersey Getting Started document.

You don't need to write a code, just call Maven command.

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example \
-DarchetypeVersion=2.27

Then, Maven creates a new project from Jersey archetype.

This Jersey project includes Grizzly.

Let's check the sample application code. There are two classes in the project: one is a resouce class, other is main class.

Here is the resource class:

package com.example;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * Root resource (exposed at "myresource" path)
 */
@Path("myresource")
public class MyResource {

    /**
     * Method handling HTTP GET requests. The returned object will be sent
     * to the client as "text/plain" media type.
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getIt() {
        return "Got it!";
    }
}

Main class is shown below.

package com.example;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

import java.io.IOException;
import java.net.URI;

/**
 * Main class.
 *
 */
public class Main {
    // Base URI the Grizzly HTTP server will listen on
    public static final String BASE_URI = "http://localhost:8080/myapp/";

    /**
     * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application.
     * @return Grizzly HTTP server.
     */
    public static HttpServer startServer() {
        // create a resource config that scans for JAX-RS resources and providers
        // in com.example package
        final ResourceConfig rc = new ResourceConfig().packages("com.example");

        // create and start a new instance of grizzly http server
        // exposing the Jersey application at BASE_URI
        return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
    }

    /**
     * Main method.
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        final HttpServer server = startServer();
        System.out.println(String.format("Jersey app started with WADL available at "
                + "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
        System.in.read();
        server.stop();
    }
}

You can see the sample is very simple.

After checking the sample, I compile the sample source code.

C:\jersey-sample\simple-service>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ simple-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\jersey-sample\simple-service\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ simple-service ---
[INFO] Compiling 2 source files to C:\jersey-sample\simple-service\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.721 s
[INFO] Finished at: 2018-07-04T19:15:21+09:00
[INFO] ------------------------------------------------------------------------
C:\jersey-sample\simple-service>

Compile is done well. After that, execute!

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ simple-service >>>
[INFO] 
[INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) < validate @ simple-service <<<
[INFO] 
[INFO] 
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ simple-service ---
Jul 04, 2018 7:29:51 PM org.glassfish.jersey.internal.Errors logErrors
WARNING: The following warnings have been detected: WARNING: HK2 service reification failed for [org.glassfish.jersey.message.internal.DataSourceProvider] with an exception:
MultiException stack 1 of 2
java.lang.NoClassDefFoundError: javax/activation/DataSource
 at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
 at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3114)
  <<snip, snip>>

NoClassDefFoundError was occurred.

If you use Java SE 8 or older, you can execute the sample rightly. The reason that Java SE 11 can't execute the sample is lack of modules about Java EE (Jakarta EE).

The removed modules are below:

  • JAX-WS
  • JAXB
  • JavaBean Activation Framework (JAF)
  • Common Annotations
  • Java Transaction API (JTA)
  • CORBA

Above execution log shows javax.activation.DataSource class was not found. DataSource class is included in JAF API.

In addition, classes included in JAXB were not found.

But, the sample don't use both JAF and JAXB. Actually, Jersey and Grizzly use JAF and JAXB.

That's why compile is OK but execution fails.

Although you don't use JAXB or other Java EE features directly, many libraries uses these features. That is pitfall of migration to Java 11.

Solution

It is not so hard to migrate the sample to Java SE 11, because the sample is not modular application.

We should only add lacked libraries to CLASSPATH.

In this case, modify pom.xml for appeding dependecies of JAXB and JAF.

Updated pom.xml is shown in below. Red means updated.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>simple-service</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>simple-service</name>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>${jersey.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>      
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0.1</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>javax.activation-api</artifactId>
            <version>1.2.0</version>
        </dependency>

        <!-- uncomment this to get JSON support:
         <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
        </dependency>
        -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.example.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <properties>
        <jersey.version>2.27</jersey.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

Changes in <plugin> elements are to update plugin version to the latest.

Changes in <dependencies> are important in this case. I added 3 libraries in <dependencies>.

  • jaxb-api
  • jaxb-impl
  • javax.activation-api

JAXB consists of 2 libraries: one is interface definition (jaxb-api) and other is implementation (jaxb-impl). javax.activation-api is JAF.

Then, clean, compile again, and execute.

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ simple-service ---
Jul 04, 2018 20:24:38 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:8080]
Jul 04, 2018 20:24:38 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...

After Jersey starts, access http://localhost:8080/myapp/myresource by browser or curl. You can see "Got it!".

 

Conclusion

  • Java 11 removes JAXB, JAF, JAX-WS and other Java EE modules actually
  • If you don't use these modules directly, many libraries uses such modules.
  • If application is not modulear application, only add removed libraries to CLASSPATH
  • In case of Maven, add removed libraries in <dependencies> of pom.xml

2018/07/04

事例から学ぶ、Java SE 11移行

このエントリーをはてなブックマークに追加

Java SE 11のリリースまで3か月を切りました。みなさん、移行の準備は進んでいますでしょうか。

Java SE 11は、新しいリリースモデルになってはじめてのLTSになるバージョンです。なので、Java 9や10はすっ飛ばして、Java 11に移行することを考えている方も多いと思います。

しかし、Java 11への移行はいろいろ大変です。

今回は簡単なサンプルをベースにJava 11への移行を考えてみます。

Jersey + Grizzly

Jerseyは、JAX-RSのRIです。ということは、私よりもこれ読んでいる人の方が絶対に詳しいはずw

そのJerseyのUser GuideのGetting Startedに書いてあるサンプルを題材にしてみましょう。

このサンプルはMavenで勝手に作ってくれます。以下はGetting Startedからの引用です。

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example \
-DarchetypeVersion=2.27

さて、とりあえず、コンパイルしてみましょう。

C:\jersey-sample\simple-service>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ simple-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\jersey-sample\simple-service\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ simple-service ---
[INFO] Compiling 2 source files to C:\jersey-sample\simple-service\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.721 s
[INFO] Finished at: 2018-07-04T19:15:21+09:00
[INFO] ------------------------------------------------------------------------
C:\jersey-sample\simple-service>

依存しているライブラリがなければ、ダウンロードするログが出るはずですが、コンパイルは成功するはずです。

では、実行してみましょう。

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ simple-service >>>
[INFO] 
[INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) < validate @ simple-service <<<
[INFO] 
[INFO] 
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ simple-service ---
7月 04, 2018 7:29:51 午後 org.glassfish.jersey.internal.Errors logErrors
警告: The following warnings have been detected: WARNING: HK2 service reification failed for [org.glassfish.jersey.message.internal.DataSourceProvider] with an exception:
MultiException stack 1 of 2
java.lang.NoClassDefFoundError: javax/activation/DataSource
 at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
 at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3114)
  <<以下、省略>>

NoClassDefFoundErrorで実行できませんでした。

このサンプルはJava 8までは普通に実行できます。でも、Java 11で実行できないのはJava EE (Jakarta EE)関連のAPIが削除されてしまったためです。

上の実行例でNoClassDefFoundErrorが出ているのは、javax.activation.DataSourceクラスがないためですが、このクラスはJavaBean Activation Framework (JAF) APIに含まれています。

他にも、JAXBのクラスもNoClassDefFoundErrorが出ています。

しかし、このサンプルのソースコードは、JAFもJAXBも使っていません。これらを使っているのは、JerseyやGrizzlyなのです。

だから、コンパイルは通るのに、実行はできないわけです。

JAXBは使っていないと安心されているかもしれませんが、多くのライブラリでJAXBやJAFを使っています。これが落とし穴になるわけですね。

解決法

では、Java 11で動作するようにしてみましょう。

今回はモジュールアプリケーションではないので、比較的簡単です。つまり、クラスパスに不足しているライブラリを追加するだけでOKです。

実際には、pom.xmlにJAXBとJAFへの依存を記述していきます。

変更したpom.xmlを以下に示します。赤字のところが変更した部分です。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>simple-service</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>simple-service</name>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>${jersey.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
     <artifactId>jaxb-api</artifactId>
     <version>2.3.0</version>
        </dependency>      
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
     <artifactId>jaxb-impl</artifactId>
     <version>2.3.0.1</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
     <artifactId>javax.activation-api</artifactId>
     <version>1.2.0</version>
        </dependency>

        <!-- uncomment this to get JSON support:
         <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
        </dependency>
        -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.example.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <properties>
        <jersey.version>2.27</jersey.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

 

pluginのところの変更はバージョンを最新にしただけです。

重要なのは<dependencies>の部分。

依存しているライブラリとして

  • jaxb-api
  • jaxb-impl
  • javax.activation-api

を追加しています。

これでビルドしなおしてから、実行してみましょう。

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ simple-service ---
7月 04, 2018 20:24:38 午後 org.glassfish.grizzly.http.server.NetworkListener start
情報: Started listener bound to [localhost:8080]
7月 04, 2018 20:24:38 午後 org.glassfish.grizzly.http.server.HttpServer start
情報: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...

ここまで表示されたら、ブラウザでもcurlでもいいので、http://localhost:8080/myapp/myresourceにアクセスしてみてください。Got it!と表示されるはずです。

 

まとめ

  • Java 11では、ほんとにJAXB、JAF、JAX-WSなどが削除されました
  • 使っていないと思っていても、意外に使われているのがJAXBとJAF
  • モジュールアプリケーションでなければ、クラスパスにJAXBとJAFのJarを追加するだけ
  • Mavenであれば、pom.xmlの<dependencies>にJAXBとJAFを追加する

 

モジュールアプリケーションの場合はどうするのかについては、需要があれば書こうと思います。