Building libraries for multiple Java versions

When migrating applications, we had to deal with the scenario that the dependent libraries might not all be migrated to Java 9. When dealing with libraries, you'll need to tackle the opposite problem. The applications consuming your library may not all be Java 9. You'll have to support Java 8 (or perhaps even older versions of Java in some cases). How do you, as a library author, create library distributions for all those versions? Before Java 9, you used to have two options:

  • You could create separate JARs for each Java version
  • In your library code, you could use reflection to do a feature check. For example, you could reflectively access a platform API that was introduced in Java 8. If it works, you are in Java 8. If not, drop down to Java 7, and so on.

Both these options are tedious. There is a new alternative with Java 9, with a feature called multi-release JARs. The concept is simple. You create a special JAR file called a multi-release JAR that contains classes for all versions of Java you are targeting.

Here's how it works. Multi-release JARs have a special structure that holds the classes within it:

Here's what you'll find in a multi-release JAR file, corresponding to the numbering in the diagram:

  1. There's a root META-INF folder with a MANIFEST.MF file that contains the following line:

Multi-Release: true

This tells the platform that this is a multi-release JAR and thus needs to be treated differently

  1. The JAR root also contains a default version of the compiled classes, just like any other JAR. Remember, this JAR targets multiple Java versions and it could hold multiple target versions of the same class. The classes at the root folder are the default base versions that could potentially apply to multiple Java versions
  2. There's a folder called versions inside META-INF. To target multiple runtimes, the JAR packages classes into sub-folders here. There's one folder for each Java version you want to target. Each such folder contains classes that have been specifically compiled for that release version. So, if the JAR is used in that version of the Java platform, the classes in the version folder override the classes in the multirelease folder and are picked up instead. If the JAR is used in a platform version that does not have classes in the META-INF folder, or the class needed doesn't exist in the version folder, the runtime falls back to the contents of the multirelease folder.

Notice that the default versions of the classes are in the root location in the JAR file. This is why you can use the JAR file with older versions of Java too. To older Java versions, a multi-release JAR file looks just like an ordinary JAR file--the root location is all the platform looks at, and the versions folder is ignored!

Let's try creating a simple multi-release JAR. The 11-migrating-application/04-multirelease-jars folder contains an extremely simple library. It's called mylib and it has a class with a method that prints the contents of a list passed to it.

We'd like to create a multi-release JAR for this library targeting two different versions of Java:

  • The base version of the library targets all pre-Java 9 versions. It contains code that performs a for loop and prints the contents of the list as follows:
        public class PrintList { 
          public void print(List<?> list) { 
            for (int i = 0; i < list.size(); i++) { 
              System.out.println(list.get(i)); 
            } 
          } 
        } 
  • The Java 9 specific version of this library has two changes--it declares itself as a Java 9 module with module-info.java and it uses forEach and a function reference to print the contents of the list, as follows:
        public class PrintList { 
          public void print(List<?> list) { 
            list.forEach(System.out::println); 
          } 
        } 

The two versions of the library are in two separate folders. Since there will be two separate versions of the same class, it helps to separate them this way.

Here's the structure of the code:

The first step to making a multi-release JAR is to add the MANIFEST.MF file that declares it. Add this file at the root of the project with a single line, shown next. Make sure you match the statement exactly without any extra spaces:

    Multi-Release: true

Now, we'll create the folders that hold the compiled classes. We'll create a folder called out and have two subfolders--base for the base classes and 9 for the Java 9 version, as shown here:

$ mkdir out
$ mkdir out/base
$ mkdir out/9

Next, we will compile the classes into these two folders by setting the right release versions. The --release parameter to the javac command lets you target specific Java versions for your compiled classes:

$ javac --release 7 -d out/base base/src/packt/mylib/PrintList.java

The preceding command compiles the PrintList.java class with target release 7, and places the complied output in the out/base directory.

Note that you don't need to have multiple versions of Java installed on your machine to achieve this. Java 9 has the ability to generate classes targeting different versions of Java by itself! This is analogous to the -target flag in Java that has been available in earlier versions of the Java platform.

Next, we'll compile the Java 9 version as follows:

$ javac --release 9 -d out/9 java9/src/packt.mylib/module-info.java java9/src/packt.mylib/packt/mylib/PrintList.java

There are two Java files this time--PrintList.java and module-info.java. The complied classes go to the out/9 directory.

Now that we have the compiled classes, it's time to create a multi-release JAR. Let's first create a JAR file with the base version classes. We also supply the MANIFEST.MF file to be included in the JAR:

$ jar -cf mylib.jar MANIFEST.MF -C out/base .

The -c option tells the jar tool to create a new JAR, and f option is used to specify the JAR file name (here, mylib.jar). The -C option changes the directory the tool is looking for to out.base and lets it compile classes there (as specified by ".").

This creates the JAR file and adds the base classes to it. Next, let's add the Java 9 classes:

$ jar -uf mylib.jar --release 9 -C out/9 .

The -u options tells the jar tool to update the JAR rather than create one. We are targeting release 9 this time, and including compiled classes in the out/9 directory.

You don't have to add all the classes in your JAR file for every version. Try to keep version-specific classes to a minimum. If there are common classes in the base version that the version-specific copy can reuse, you just don't include it here. The platform will fall back to the base folder for classes it doesn't find for that specific version.

Here are the contents of the JAR file that's generated. This is the structure we have already seen:

Remember that the multi-release JAR feature was introduced in Java 9. So, you cannot really create version-specific alternatives in your JAR for Java 8 or earlier. Those versions of the platform will not know to read from the META-INF/versions folder. They'd just use the compiled classes in the JAR root folder. This is, however, a good feature to use if you need to create new Java-9-only classes. Since those classes will end up in the META-INF/versions folder, older platforms will ignore them. Once future versions of Java are released, this feature can be used for those versions too. So, you can have a META-INF/versions/10 folder targeting the Java 10 platform, for example.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.26.217