1. Overview

The Java platform used to have a monolithic architecture, bundling all packages as a single unit.

In Java 9, this was streamlined with the introduction of the Java Platform Module System (JPMS), or Modules for short. Related packages were grouped under modules, and modules replaced packages to become the basic unit of reuse.

In this quick tutorial, we’ll go through some of the issues related to modules that we may face when migrating an existing application to Java 9.

2. Simple Example

Let’s take a look at a simple Java 8 application that contains four methods, which are valid under Java 8 but challenging in future versions. We’ll use these methods to understand the impact of migration to Java 9.

The first method fetches the name of the JCE provider referenced within the application:

private static void getCrytpographyProviderName() {
    LOGGER.info("1. JCE Provider Name: {}\n", new SunJCE().getName());
}

The second method lists the names of the classes in a stack trace:

private static void getCallStackClassNames() {
    StringBuffer sbStack = new StringBuffer();
    int i = 0;
    Class<?> caller = Reflection.getCallerClass(i++);
    do {
        sbStack.append(i + ".").append(caller.getName())
            .append("\n");
        caller = Reflection.getCallerClass(i++);
    } while (caller != null);
    LOGGER.info("2. Call Stack:\n{}", sbStack);
}

The third method converts a Java object into XML:

private static void getXmlFromObject(Book book) throws JAXBException {
    Marshaller marshallerObj = JAXBContext.newInstance(Book.class).createMarshaller();
    marshallerObj.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    StringWriter sw = new StringWriter();
    marshallerObj.marshal(book, sw);
    LOGGER.info("3. Xml for Book object:\n{}", sw);
}

And the final method encodes a string to Base 64 using sun.misc.BASE64Encoder, from the JDK internal libraries:

private static void getBase64EncodedString(String inputString) {
    String encodedString = new BASE64Encoder().encode(inputString.getBytes());
    LOGGER.info("4. Base Encoded String: {}", encodedString);
}

Let’s invoke all the methods from the main method:

public static void main(String[] args) throws Exception {
    getCrytpographyProviderName();
    getCallStackClassNames();
    getXmlFromObject(new Book(100, "Java Modules Architecture"));
    getBase64EncodedString("Java");
}

When we run this application in Java 8 we get the following:

> java -jar target\pre-jpms.jar
[INFO] 1. JCE Provider Name: SunJCE

[INFO] 2. Call Stack:
1.sun.reflect.Reflection
2.com.baeldung.prejpms.App
3.com.baeldung.prejpms.App

[INFO] 3. Xml for Book object:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book id="100">
    <title>Java Modules Architecture</title>
</book>

[INFO] 4. Base Encoded String: SmF2YQ==

Normally, Java versions guarantee backward compatibility, but the JPMS changes some of this.

3. Execution in Java 9

Now, let’s run this application in Java 9:

>java -jar target\pre-jpms.jar
[INFO] 1. JCE Provider Name: SunJCE

[INFO] 2. Call Stack:
1.sun.reflect.Reflection
2.com.baeldung.prejpms.App
3.com.baeldung.prejpms.App

[ERROR] java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext
[ERROR] java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder

We can see that the first two methods run fine, while the last two failed. Let’s investigate the cause of failure by analyzing the dependencies of our application. We’ll use the jdeps tool that shipped with Java 9:

>jdeps target\pre-jpms.jar
   com.baeldung.prejpms            -> com.sun.crypto.provider               JDK internal API (java.base)
   com.baeldung.prejpms            -> java.io                               java.base
   com.baeldung.prejpms            -> java.lang                             java.base
   com.baeldung.prejpms            -> javax.xml.bind                        java.xml.bind
   com.baeldung.prejpms            -> javax.xml.bind.annotation             java.xml.bind
   com.baeldung.prejpms            -> org.slf4j                             not found
   com.baeldung.prejpms            -> sun.misc                              JDK internal API (JDK removed internal API)
   com.baeldung.prejpms            -> sun.reflect                           JDK internal API (jdk.unsupported)

The output from the command gives:

  • the list of all packages inside our application in the first column
  • the list of all dependencies within our application in the second column
  • the location of the dependencies in the Java 9 platform – this can be a module name, or an internal JDK API, or none for third-party libraries

4. Deprecated Modules

Let’s now try to solve the first error java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext.

As per the dependencies list, we know that java.xml.bind package belongs to java.xml.bind module which seems to be a valid module. So, let’s take a look at the official documentation for this module.

The official documentation says that java.xml.bind module is deprecated for removal in a future release. Consequently, this module is not loaded by default into the classpath.

However, Java provides a method to load modules on demand by using the –add-modules option. So, let’s go ahead and try it:

>java --add-modules java.xml.bind -jar target\pre-jpms.jar
...
INFO 3. Xml for Book object:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book id="100">
    <title>Java Modules Architecture</title>
</book>
...

We can see that the execution was successful. This solution is quick and easy but not the best solution.

As long term solution, we should add the dependency as a third party library using Maven:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.4.0-b180725.0427</version>
</dependency>

5. JDK Internal APIs

Let’s now look into the second error java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder.

From the dependencies list, we can see that sun.misc package is a JDK internal API.

Internal APIs, as the name suggests, are private code, used internally in the JDK.

In our example, the internal API appears to have been removed from the JDK. Let’s check what the alternative API for this is by using the –jdk-internals option:

>jdeps --jdk-internals target\pre-jpms.jar
...
JDK Internal API                         Suggested Replacement
----------------                         ---------------------
com.sun.crypto.provider.SunJCE           Use java.security.Security.getProvider(provider-name) @since 1.3
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8
sun.reflect.Reflection                   Use java.lang.StackWalker @since 9

We can see that Java 9 recommends using java.util.Base64 instead of sun.misc.Base64Encoder. Consequently, a code change is mandatory for our application to run in Java 9.

Notice that there are two other internal APIs we’re using in our application for which Java platform has suggested replacements, but we didn’t get any error for these:

  • Some internal APIs like sun.reflect.Reflection were considered critical to the platform and so were added to a JDK-specific jdk.unsupported module. This module is available by default on the classpath in Java 9.
  • Internal APIs like com.sun.crypto.provider.SunJCE are provided only on certain Java implementations. As long as code using them is run on the same implementation, it will not throw any errors.

In all the cases in this example, we’re using internal APIs, which is not a recommended practice. Therefore, the long term solution is to replace them with suitable public APIs provided by the platform.

6. Conclusion

In this article, we saw how the module system introduced in Java 9 can cause migration problems for some older applications using deprecated or internal APIs.

We also saw how to apply short term and long term fixes for these errors.

As always, the examples from this article are available over on GitHub.

Course – LS (cat=Java)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.