How to Migrate to Java 9? It’s Easy if You Do It Smart

Use this practical guide to learn everything you need to know about how to migrate successfully from Java 8 to Java 9. Expert tips from certified programmers included.

Since 1995, Java has been constantly evolving and became one of the most used programming languages in the world. And in 2017, the 9th major release of the Java Development Kit (JDK) came out, three years after the release of JDK 8.

As you know, each Java release has added a set of new features – small, medium or large ones and the new Java SE 9 is not an exception.

Now you probably are hatching out the development plan and looking for the timing to migrate your current projects on it. Yet before putting your plans into action, you should take into consideration a few important points covered in this article. Here you will find out how to migrate your application to Java 9, check some of the common problems you may struggle with, and learn the strategies to solve them.

So, here are some of the most important topics we are going to look at in this Java 9 migration guide:

  • How to migrate from Java 8 to Java 9 and how to make Java 9 migration easier
  • When to migrate to Java 9
  • How to use the JDeps tool to inspect the code
  • Migrating to Java Jigsaw modules
  • How to solve migration issues like encapsulated internal APIs, split packages, cyclic dependencies, and more
  • How to combine the module path and the classpath to provide backward compatibility
  • How to make a Java application compatible with JDK 9
  • And, should you outsource Java 9 migration.

Let’s dig in!

First Steps to Start Migrating Your Project to Java Jigsaw Modules

Migrating Your Project to Java Jigsaw Modules There are a few basic preparing processes you should perform in order to migrate software to Java 9 (assuming it’s running on Java 8):

  1. Install Oracle JDK 9 release.
  2. Move the application to Java 9 using classpath.
  3. Run application on JDK 9 to identify errors and warnings.
  4. Upgrade all third-party libraries and tools to the latest versions that support JDK 9.
  5. Recompile application on JDK 9.
  6. Use the JDeps tool to check if the internal APIs can be replaced with other ones supported by Java 9.

Sounds easy, right?

Yet, as we already know, the JDK-internal APIs aren’t accessible anymore and your code that depends on these APIs will break. In this case, you should use Java Dependency Analysis (JDeps) tool to check if the JDK-internal classes are used in your code.

In a nutshell, JDeps is a tool for analyzing and finding statical dependencies. When JDeps finds a dependency in your code it gives hints and proposes alternatives for these classes.

But it’s still your responsibility to decide which library to use to get rid of these classes or replace them. If you fail in your attempt to find the appropriate substitutes for the JDK-internal classes, Java 9 will help you with an alternative – the compiler command-line option –add-exports.

In this case, you don’t need to make changes to your code. All you have to do is to adjust the scripts to add this option during the compilation time as this command breaks the encapsulation and makes the internal APIs accessible to your code.

Secondly, you should check your code for dependencies on the rt.jar and tools.jar files as both of them were removed in Java 9.

Now, let’s dive deeper into the details.

Migrating to Java 9 and Possible Challenges You Might Face

One of the strongest things in Java that made it so successful through years is that each Java release is backward compatible with its earlier versions. Yet Java 9 with its JDK modularization is such an explosive shift that backward-compatibility can’t be 100% assured. However, all the necessary changes depending on the used libraries and the code structure.

So before making a decision to migrate to Java 9, it’s important to know the result you want to achieve:

  • You just want to make your Java application simply run on JDK 9 and you don’t want to define any modules inside the code (Only the classpath is used).
  • You want to modularize only a part of an application and keep the rest of it not modularized (Both the classpath and the module path are used).
  • You want to modularize the whole application (Only the module path is used).

In the first case, you just have to make sure that your application runs on Java 9 without the creation of any modules. To do that, start with setting the JAVA_HOME environment variable to point to the JDK 9 installation, and then compile and run your application without implementing any changes in the code. This means you stay on the classpath and don’t use the module path at all.

Yet, there are disadvantages of relying just on the classpath and one of them is that you can’t create custom runtime images.

With the second approach, you modularize only a part of your application and keep the other part without changes which means that the module path and the classpath are used together. Although, there’s an issue – by default the code from the module path can’t access the types that were put in the classpath. Fortunately, there are at least two ways to solve this issue:

  • The first solution is to take the code from the classpath and turn it into automatic modules. This will be useful, especially for third-party JAR files that have not yet been modularized.
  • Another solution would be to use a new command-line option called -add-exports. This option exports the packages, so they can be accessed from other modules or from the classpath.

The third case involves the complete modularization of your application. As a result, the classpath won’t be used anymore and each piece of code will be divided into modules and defined by a module descriptor – module-info.java.

What are Automatic Modules?

Three different types of modules have been introduced by Oracle to help developers migrate successfully from Java 8 to Java 9 and make the process as easier as it can be:

  1. Unnamed modules are created by placing a JAR file on the classpath (i.e. what happens in the current Java world).
  2. Automatic modules are created when a JAR file is placed on the new module path.
  3. Explicit/named modules are defined manually with the new module-info.java.

An automatic module is a special type of named module that is automatically generated after placing a JAR file into the module path. They are used to ease the migration of existing applications to Java 9 and to accomplish the backward-compatibility. Also, they permit you to start modularizing your own code without waiting while all the needed libraries and frameworks will be modularized.

As an example, let’s consider the widespread Log4j library. If you put the Log4j JAR file on the module path, you can use it in your module by requiring it inside the module descriptor of your application:

module com.romexsoft.testModule {
requires log4j;
}

In this example, the Log4j is turned into an automatic module and it can be used in your modular application. Then you can access all the packages from the Log4j module as an automatic module exports all its packages by default.

Also, it’s important to know that an automatic module has a few crucial characteristics:

  • It exports and opens all of its packages:
    • All the packages from an automatic module are exported in order to be accessible both at compile-time and at runtime.
    • All the packages from an automatic module are opened in order to be accessible with deep reflection.
  • It doesn’t have the module-info.class file in its top-level directory.
  • It can access every class from the unnamed module (from the classpath).
  • It can’t declare that it has any dependencies on any other modules.

What’s more, when using an automatic module, its name is generated by Jigsaw from the name of the JAR file. The main aspects of the filename-based algorithm are described below:

  • The .jar suffix is removed from the name of the JAR file. And the resulting string is further used for determining and extracting the name and the version of an automatic module.
  • The module name is extracted. According to the JDK 9 API documentation, “if the name matches the regular expression -(\\d+(\\.|$)), then the module name will be derived from the subsequence preceding the hyphen of the first occurrence. The subsequence after the hyphen is parsed as a version and it is ignored if it can’t be parsed as a version.”
  • Some replacements on the name of the module are performed. As JDK 9 API documentation states, “all non-alphanumeric characters ([^A-Za-z0-9]) in the module name are replaced with a dot (‘.’); all repeating dots are replaced with one dot; all leading and trailing dots are removed.”

And you need to be ready that a fatal error will be shown after you will place a JAR on the module path for which the module name can’t be extracted. The name of the automatic module can also be defined directly inside the MANIFEST.MF file from the META-INF directory of the JAR file:

Automatic-Module-Name: testModule

Now, that we’ve reviewed almost everything we need to know about automatic modules, it’s time to consider the JDeps tool, which is a crucial tool used to find library dependencies.

The Java Dependency Analysis Tool as the Capability to Find Inappropriate Dependencies

Are you relying on Sun internal packages (like sun.misc.*) within your code? Then I have bad news for you – your code will stop running on Java 9 because of its new restrictions. Even if you don’t depend on Sun internal packages yourself yet have transitive dependencies on libraries that depend on these packages, then they will break and your code will break too!

Fortunately, Oracle foresaw such scenarios and provided a tool called “JDeps”.

So, what is JDEP? The Java Dependency Analysis Tool (JDeps) is a very useful command-line tool used for different purposes:

  • to discover all the static library dependencies;
  • to discover the possible usages of internal JDK APIs;
  • to automatically generate a module descriptor for a JAR file.

In a nutshell, JDeps has an option called JDK-internals that finds dependencies on any unsupported JDK internal APIs that are private to the JDK implementation. And its syntax is as follows:

jdeps –jdk-internals –class-path <input_file>

As an input, we can specify a JAR file or a .class file that should be analyzed.

Also, JDeps suggests replacements for the internal APIs found. And in case you want to successfully migrate from Java 8 to Java 9 (and you do!), the JDeps tool will help you check whether your JAR files from the classpath are using the JDK internal APIs.

Encapsulated JDK 9 Internal APIs

As you might know, Java has two categories of APIs in the JDK: supported and unsupported.

The supported APIs comprise JCP standard APIs like java.* and javax.*, JDK- internal APIs like com.sun.* and JDK.*.

The unsupported APIs include the sun.* packages. These APIs were never intended for an external use yet many developers used the sun.* packages outside the JDK.

*Note: According to Oracle research, the most used JDK internal classes: sun.misc.BASE64Encoder, sun.misc.BASE64Decoder, and sun.misc.Unsafe.

And what’s important, Java 9 encapsulated almost all JDK internal APIs. Although this probably won’t be the biggest issue you will have to struggle with during the migration to JDK 9 yet this one may cause some problems within the process.

Luckily, there are two independent solutions that should help solve the problem of JDK internal APIs:

  • Replace each of your JDK internal APIs with supported APIs.
  • Keep the existing JDK internal APIs and use the –add-exports command-line option to break the encapsulation to make the JDK internal APIs accessible to code in other modules or to code on the classpath.

The –add-exports option added to the Java compiler (javac) exports a package to a specifically named module or to the unnamed one.

Below is the syntax of the –add-exports command-line option:

–add-exports <source_module>/<name_of_package_to_be_exported>=<list_of_target_modules>

  • <source_module> represents the module where the package that should be exported is located.
  • <name_of_package_to_be_exported> represents the name of the package that should be exported to the <list_of_target_modules>.
  • <list_of_target_modules> represents a comma-separated list of modules that will gain access to the exported package.

Here’s an example of how it might look in your project:

–add-exports java.base/sun.net=com.romexsoft.testmodule

In the snapshot above, we pass the module where the package is located (java.base) and the module where the package should be exported (com.romexsoft.testmodule). By compiling our application using the option –add-exports mentioned above, the package sun.net will be exported to our com.romexsoft.test module.

In case, some part or your code is on the classpath it’s possible to use the constant ALL-UNNAMED that represents the entire classpath. For example, below the command exports the sun.net package to the classpath so it could be accessed from the entire code on the classpath:

–add-exports java.base/sun.net=ALL-UNNAMED

Also, you can use another option to specify the attribute Add-Exports in the MANIFEST.MF file of a JAR file. For instance, in order to export the package sun.net from module java.base to the unnamed module:

Add-Exports: java.base/sun.net

To sum up, both approaches are solving the issue.

The first solution (replace all JDK internal APIs) is by far the best one as you completely get rid of the unsupported JDK APIs in your code. And considering that the JDK internal APIs are marked as deprecated, it’s wise to provide replacements for them as soon as possible.

The second solution is reasonable only if all you need is to make your code compile and run again using JDK 9. However, Oracle has stated that the JDK unsupported APIs will be removed in the next major JDK release. This may be JDK 10 or later. Therefore, adding the –add-exports option is just a temporary solution to make your code work and sooner or later you’ll have to replace them with the supported JDK APIs. It all depends on how long the unsupported JDK APIs that you’re using will remain in the JDK and will not be removed.

Opening Packages for Deep Reflection

As I have already mentioned, deep reflection is by default allowed in a named module for the code on the classpath, but also by default it’s not accessible to code in another named module.

In order to allow reflective access from code in a named module to code in another named module, you can use the new –add-opens command-line option. It’s used to provide deep reflective access from one module to another one or to the code on the classpath. The syntax of the add-opens command-line option goes like this:

–add-opens <source_module>/<name_of_package_to_be_opened>=<list_of_target_modules>

The package defined and located in the <source_module> is opened for deep reflective access to the modules listed in the <list_of_target_modules>. These modules will be able to access the package at runtime only using deep reflection but won’t be able to access the package during compilation.

If you put the constant ALL-UNNAMED instead of the <list_of_target_modules>, then the entire code on the classpath will be able to access the package at runtime using deep reflection.

Deep reflection can take place only at runtime. As a result, the –add-opens command-line can’t be used at compile-time using the javac command.

How to Fix the Split Package Issue

Before going into detail about split packages I would like to mention that this is one of the most serious problems that may occur in the Java 9 modular world and you will need to pay great attention to solving this kind of issue. Yet it’s good you’ve found our guide as below we have considered several possible ways of how to solve the split package problem. You can choose one of the approaches or go on with your own.

So, what so special about split packages?

Split packages occur when two or more members of a package are split over more than one module. To enforce reliable configuration a module is not allowed to read the same package from two different modules. The actual implementation is stricter, though, and two modules (no matter what kind of modules!) are not allowed to even contain the same package (exported or concealed).

By default, the module system operates under this assumption, and whenever a class needs to be loaded, it is looking for a module that contains this package and looks for a class there. The reason is that the system loads all the modules from the module path with a single class loader which can’t have more than one single type of a package. Even if the modules contain different sub-packages, the split package problem will happen as they share a package with the same name.

It’s also important to mention that packages from the platform modules may cause the split package problem as well. For example, this will happen if you will develop a module that uses a package name that is already presented in one of the platform modules. And it doesn’t matter if the packages are exported, open, or concealed – the split package problem will occur anyway.

Although, during the migration from Java 8 to Java 9 the situation described will differ. Your code comes from the classpath which sets it into the unnamed module. To maximize compatibility, it’s not scrutinized and not all the described above module-related validations are applied. However, if you decided to modularize your application there’s no universal solution to repair the split package problem.

So let’s consider some of the most recommended solutions to fix the split packages issue:

  • If you have two JAR files that share a package with the same name, then you can make a single JAR file by unzipping them in the same directory and then zipping the entire directory into a single ZIP file. This way you will have one single JAR that can be set into the module path and that will have only one automatic module. The package now will be in a single module and split packages are no longer an issue.
  • Another way is to try to rename one of the packages. The probability of success depends on the structure of the classes and, especially if the classes live in the same namespace or not.
  • The third option is to check if one of the JARs can be ultimately replaced by another one. If there’s a chance to replace the JAR with another one, you should try it.
  • The last solution that you can apply to your code is to take the entire packages that cause the split package problem from both modules and move them into a third new module, which exports the packages you need inside your module.

Also, as JEP 200 states, you need to remember that “a non-standard module must not export any standard API packages.” This means that if you have your own module com.romexsoft.testModule, you shouldn’t, for example, export the java.sql package, because the java.sql package is already exported by the java.sql platform module. Additional export will lead to split packages.

Removed Tools and Components

Application Migration to Java 9 list Another change with a definitely greater impact on how Java developers will do the coding is the elimination of the rt.jar (runtime), tools.jar, and dt.jar.

This event might have a consequence on your app if you make assumptions throughout your code based on one of these three JAR files. Calling the ClassLoader::getSystemResource() method in JDK 9 won’t return the URL to a JAR file. Instead, it will return a valid one.

Yet if you call the method getSystemResource() with the parameter java/lang/Class.classClassLoader.getSystemResource(“java/lang/Class.class”) the following URL will be returned: jrt:/java.base/java/lang/Class.class

You need to be aware of these new changes and check if your code expects to receive this URL in a certain format, which now may be different.

Removed Methods in JDK 9

The following methods have been completely eliminated in Java 9:

  • java.util.logging.LogManager.addPropertyChangeListener
  • java.util.logging.LogManager.removePropertyChangeListener
  • java.util.jar.Pack200.Packer.addPropertyChangeListener
  • java.util.jar.Pack200.Packer.removePropertyChangeListener
  • java.util.jar.Pack200.Unpacker.addPropertyChangeListener
  • java.util.jar.Pack200.Unpacker.removePropertyChangeListener

This means that during the development process using JDK 9 you need to make sure that none one of these six methods is used. Otherwise your code will break at the compile time.

The positive thing is that the chance of having at least one of them in your code is too low.

Cyclic Dependencies

When to Start Application Migration to Java 9 Cyclic dependency is a relation between two or more modules and is expressed by the fact that the modules are directly or indirectly dependent on each other.

In Java 9, such kind of module relations isn’t allowed at the compile-time or link-time. Jigsaw deliberately imposes a cyclic check of dependencies during the compilation and in case two modules contain a cyclic dependency, the compilation will fail.

However, cyclic dependencies are allowed during the runtime yet only after the module graph is already resolved. During the runtime, you can introduce a cyclic dependency using the –add-reads option as the module graph has already been resolved before.

The reasons for the exclusion of cyclic dependencies are justified: to simplify the module system or to make the module graph more understandable. Two modules that require each other would be better represented as a single module.

Within the process of development, cyclic dependencies can be solved with the help of interfaces to decouple the linking between modules. This can be implemented using the Service Provider API and by applying Service consumers and Service providers. Then the module should depend on the interface and not on another module.

Now that we have discussed possible scenarios you may get into while migrating to Java 9 and answered how to make Java 9 migration as easier as it can be, let’s move to the next hot topic of our guide – when it’s high time to migrate software to Java 9.

So, When to Start Application Migration to Java 9

clock - migrate to Java 9 As you know, Java 8 has raised the software development to a new level. It gave us a lot of new features and significantly altered the Java development process so far.

However, we at Romexsoft are always looking for tools that improve our daily work, help us write code better, faster, and less vulnerable. This makes the new language features worth the evaluation. In addition, we expect that various libraries and tools will soon begin to take advantage of Java 9 and after a while, the use of JDK 9 will become a necessity.

For us, as an outsourcing Java development company, it’s critical to test all the components by ourselves and migrate to Java 9 as soon as possible. Yet it’s not the only trigger. We also expect performance growth from new features such as factory methods for immutable collection, private interface methods, improvements to the Process and Stream API, and the modular architecture.

Also, the reasonable approach to use Java 9 features is to start a new project based on JDK 9. It will allow you to take advantage of new attributes without the need of completely modifying the approaches of developing current projects. This option will allow you to try out new technologies on small projects (which is much easier and safer!) and then with a bunch of experience migrate existing large enterprise applications.

So, an answer to “when to migrate to Java 9” is: right after you finish programming an app on JDK 9 from scratch.

Of course, refactoring of an existing application will allow you to gather lots of useful knowledge and invaluable hands-on experience yet this is step #2.

Eliminating the Risks

The bottom line is it’s going to take time to migrate to JDK 9 as it’s a multi-step process, depending on the size and the libraries software is using, yet it’s worth the efforts.

Hopefully, the migration to the modular architecture won’t be too painful. However, it will require a lot of patience and hard work on the developers’ side and, especially, in the early stages when things love to break and behave unexpectedly.

In this practical guide to Java 9 migration, we have overviewed the solutions to some of the most common obstacles that can happen. Knowing how to fix these will give you strong chances to overcome all migration issues, make your application compile and run it on Java 9.

Hire Java programmers?

If migrating to Java 9 is what you are looking for yet your development team is new to this topic, contact Romexsoft and we will help you migrate from Java 8 to Java 9 successfully! Read more about our Java development services

Written by Romexsoft on February 28th, 2019 (edited 2020)
Author: Yura Bondarenko

Share The Post