Migrating to Java 11 from Java 8

Krunal Hedaoo
Globant
Published in
5 min readAug 24, 2021

Overview

Oracle released Java 11 in September 2018 which is a long-term support (LTS) release after Java 8 and has also stopped supporting Java 8 in January 2019. As a result, most of the companies decided to upgrade to Java 11 to get the latest updates or else they have to pay to receive Oracle JDK 8 support from Oracle or stay with Oracle JDK 8 without receiving any updates, which is not recommended.

In this article, we will try to understand what it takes to migrate a maven based spring boot application from Java 8 to Java 11. We will take a look at some prerequisites, changes to be done based on libraries being deprecated in the newer version and few issues that might occur after moving the application to Java 11.

Let us look at some prerequisites first.

1. Java Version

Upgrade the java.version property in your pom.xml.

From:
<java.version>1.8</java.version>
To:
<java.version>11</java.version>

Make sure you are not changing Java version from 1.8 to 1.11, which will downgrade your application to Java 1 and you don’t want to do that, right? 😄

2. Maven Compiler

If you are using the older version of maven, upgrade it the latest version and also update the source and target to 11.

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>

Similarly, we can define the version using properties as:

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>

3. Spring Boot

Upgrade the spring boot version to 2.1.X since 2.0.X and older versions does not support Java 11. In our project, we have gone for 2.3.4.RELEASE version.

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</plugin>

Upgrading the spring boot to 2.3.4.RELEASE will also upgrade its corresponding dependencies like

Spring Framework to 5.2.9.RELEASE
Tomcat to 9.0.38
Hibernate to 5.4.21.Final, etc.

Click here to find the complete list of dependencies and its compatible versions.

Note: If your project is only Spring based then upgrading the Spring Framework version to 5.1 will work as Spring 5.1 and higher versions supports Java 11.

If you don’t use Spring or Spring Boot…

As most of us know that Java has been modularized from Java 9 onwards and there are multiple API and modules which are no longer part of JDK and needs to be added explicitly if we want to use them. JAXB, JavaFX and JAX-WS are few of the modules which were deprecated in Java 9 and removed in Java 11.

Click here to see the Removal of the Deployment Stack.

If your project need JAXB dependencies then below dependencies should be added to pom.xml explicitly.

<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-core</artifactId>
<version>2.3.0</version>
</dependency>

<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>

4. Docker

If your application is a containerized application then you also need to update the docker image in Dockerfile. We have gone for Azul OpenJDK image.

FROM azul/zulu-openjdk-alpine:11

Zulu is an OpenJDK distribution from Azul. You can go for any other Linux distribution as well like Debian, centos, etc.

Click here for more details on Azul Zulu distributions.

Now, let us try to understand some dependency changes and the corresponding code level changes to be incorporated because of removal or deprecation of few libraries in the new spring boot version and java 11.

1. GuavaCacheManager to CaffeineCacheManager

Support for Guava has been dropped in Spring Framework 5. If you were are using GuavaCacheManager, then you need to move to CaffeineCacheManager which also needs an additional Caffeine library to be added to pom.xml.

<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>

The corresponding java code changes will be as below:

From:
GuavaCacheManager guavaCacheManager = new GuavaCacheManager();
guavaCacheManager.setCacheBuilder(CacheBuilder.newBuilder()
.expireAfterWrite(1, CACHE_TIME_UNIT));

To:
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(1, CACHE_TIME_UNIT));

Click here to get more details about the cache manager library change.

2. Change in StringUtils package base package

As part of spring boot version upgrade there is also change in commons-lang library version. It will upgrade to

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>

and the base package of Apache Commons Lang 3 is not org.apache.commons.lang anymore. If you are using commons-lang library then you need to change its package to org.apache.commons.lang3.

From:
org.apache.commons.lang.StringUtils in Lang 2.6
To:
org.apache.commons.lang3.StringUtils in Lang 3

3. Switching to BouncyCastle API

If you are using sun.security.* packages in your code, then you need to move to BouncyCastle API (http://www.bouncycastle.org/) since from java 9 com.sun.* and sun.* package is not directly accessible in the code and Oracle “protect” these packages even in compile time. The reason being com.sun.* and sun.* package hold internal stuff, and should not be used by third-party applications in general case.

Make sure you add the below dependency to your pom.xml in order to use Bouncycastle API.

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>

4. Mockito changes

A new version of Mockito comes from the upgraded spring boot version and there are few changes to be made based on that to make your older code work.

a. MockitoAnnotations.initMocks(this) to MockitoAnnotations.openMocks(this)
b. org.mockito.runners.MockitoJUnitRunner to org.mockito.junit.MockitoJUnitRunner
c. import static org.mockito.Matchers to import static org.mockito.ArgumentMatchers

5. GC logs changes

Garbage collection (GC) logging uses the JVM unified logging framework, and there are some differences between the new and the old logging. So flags like PrintGCDetails, PrintGCDateStamps don’t work anymore. Also, the Garbage-First Garbage Collector (G1 GC) is the default garbage collector in JDK 9 and later releases. Taking this into account you may need to update your JVM logging options. We have made the changes to our JVM options as shown below.

From:
-server -Xms2G -Xmx2G -XX:PermSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=20 -XX:ConcGCThreads=5 -XX:InitiatingHeapOccupancyPercent=70 -XX:SurvivorRatio=8 -XX:+PrintGCDateStamps -verbose:gc -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

To:
-server -Xms2G -Xmx2G -XX:PermSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=20 -XX:ConcGCThreads=5 -XX:InitiatingHeapOccupancyPercent=70 -XX:SurvivorRatio=8 -Xlog:gc* -XX:+HeapDumpOnOutOfMemoryError

Click here to get more insights on the GC related changes required.

Finally, let us look at few issues which you might face while upgrading your java and spring boot version.

1. Spring Boot — BeanDefinitionOverrideException: Invalid bean definition

Bean overriding has been disabled by default since spring boot 2.1 and has to be enabled explicitly by using below property.

spring.main.allow-bean-definition-overriding=true

2. Caused by: java.lang.ClassNotFoundException: org.joda.time.YearMonth

This issue might occur if you are using old version of joda-time dependency. You need to update the version to 2.x version.

<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.10</version>
</dependency>

3. Test cases failure with any(T) method.

After upgrading the Mockito version you might see few of the test cases failing which uses the any(T.class) method. In older versions of Mockito any(T.class) used to mean “any reference including null, cast to the type T to avoid an explicit cast” but with 2.x version it has changed to “any instance of class T” and thus any(T.class) would stop matching null during an upgrade from 1.x to 2.x. We can avoid this error by changing the code as below.

From:
any(T.class)
To:
ArgumentMatchers.nullable(T.class)

Click here to find out what’s new in Mockito 2.

Conclusion

This was the approach I had followed for migrating our application to java 11. I hope the above steps would help you in your project migration. You will also find some other issues as well based on the dependencies you have as part of your project; feel free to share your observations as well in the comments so that we together make java 11 migration process as easy as possible.

Thank you for reading this article.

Happy Learning!

--

--

Krunal Hedaoo
Globant
Writer for

A Java developer by profession always trying to learn the nuances of the language and enhance the technical knowledge.