Spring Boot 1.5.x to 2.0.x Migration – first step of migration to Java 11

17/12/2018

For a long time, we have relied on Java 8 for most of our projects. But with recent announcements from Oracle, the situation has changed and we had to plan some actions.

The commotion around Java is caused by the fact that Oracle has announced that, effective January 2019, ”Java SE 8 public updates will no longer be available for “Business, Commercial or Production use” without a commercial license.” The solution is Java 11 (JDK 11), as it is officially out as the Java Long-Term Support (LTS) release. Java Open JDK 11 version will be interchangeable with Oracle JDK version.

As announced in September 2017, with the Oracle JDK and builds of Oracle Open JDK being interchangeable for releases of Java SE 11 and later ones, the Oracle JDK will primarily be for commercial and support customers, while Open JDK builds from Oracle are for those who do not want commercial support or enterprise management tools.

There is also a declaration of six month free updates for Open JDK from Oracle. For these reasons, migration from Java 8 to 11 is a good idea. Actually, the safest path for growing your Java projects seems to be prepared for upgrading with each new release.

There are of course alternatives. You can use JDK from other vendors like: Azure, IBM, RedHat or recently announced Amazon.

 

Migration of large, complex codebase from Java 8 and Spring Boot 1.5.x to Java 11 and Spring Boot 2.x

In this series of articles we will present steps we had to perform to successfully migrate large and complex codebase to Spring Boot 2.x and Java 11. The project is microservices based insurance system. It’s been actively developed for 3 years, with the first production release in 2016.

First, the plan is to upgrade the project from Spring Boot 1.5.x to 2.x, and then to upgrade to Java 11.

 

Starting point

The project first commit is from September 2015 (developed ever since by team of 20+ members) and current production version consists of circa 6191 files. The project is based on microservices and it contains 32 separate backend modules.

These numbers should give you a brief glimpse of the project size. It is safe to say that this is a pretty big project. The technologies used in the project are mainly: Spring Boot, Spring Cloud, Zuul, Redis, JWT, Swagger, Feign, Hibernate. Maven is used as a build tool.

To migrate to Java 11 we plan the following milestones:

  1. Change project from Spring Boot 1.5.x to 2.0.x
  2. Change project from Spring Boot 2.0.x to 2.1.0
  3. Change Java 8 to Java 11

Why the first step is Spring Boot update?

Based on Wiki:

Spring Boot 2 is the first version to support Java 9 (Java 8 is also supported). If you are using 1.5 and wish to use Java 9 you should upgrade to 2.0 as we have no plans to support Java 9 on Spring Boot 1.5.x.

Java 10 is supported as of Spring Boot 2.0.1.RELEASE.
Java 11 is supported as of Spring Boot 2.1.0.M2.

Our plan for step one is outlined below:

  1. Build 3 modules on Spring Boot 2 and successfully run them locally.
  2. Build all modules on Spring Boot 2 and successfully run them locally.
  3. Deploy modules to test environment and test them.

 

Let’s go

At first, I had included this dependency in these modules:

<dependency>
     <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-properties-migrator</artifactId>
</dependency> 

It helps with Spring Boot migration process.

The basic change I had made after that was changing the Spring Boot version:

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.3.RELEASE</version> 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.0.3.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency> 

I had also changed the Spring Cloud dependency version:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-parent</artifactId>
    <version>Finchley.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency> 

As it was based on SpringBoot 2.0.3.RELEASE, I had made these dependencies compatible with each other based on this matrix.

First run resulted in errors saying that there were missing versions of some dependencies (i.e. guava, jwt, javax.inject, spock-spring, hibernate-validator). We had to adjust dependencies and change feign dependency to more recent:

From:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency> 

To:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency> 

Also there were many issues with package change etc.

 

Changes in imports in our microservices


import org.springframework.boot.web.support.SpringBootServletInitializer;
// changed into:
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;


import org.springframework.cloud.netflix.feign.EnableFeignClients;
// changed into:
import org.springframework.cloud.openfeign.EnableFeignClients;


import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
// changed into:
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;


import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
// changed into:
import org.springframework.boot.jdbc.DataSourceBuilder;


import org.springframework.data.querydsl.QueryDslPredicateExecutor;
// changed into:
import org.springframework.data.querydsl.QuerydslPredicateExecutor;


import org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor;
// changed into:
import org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor;

The annotation below had been deleted and it did not really impact future process:


@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER) 

 

Redis default implementation

Redis changed default implementation from Jedis to Lettuce. Because of that, I needed to add an additional dependency (commons-pool2):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency> 

And changing config from:

import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
(...)

@Bean
JedisConnectionFactory redisConnectionFactory() {
   JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
   redisConnectionFactory.setHostName(hostName);
   redisConnectionFactory.setPort(port);
   return redisConnectionFactory;
}

To:

import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
(...)

@Bean
LettuceConnectionFactory lettuceConnectionFactory() {
   LettuceConnectionFactory redisConnectionFactory = new LettuceConnectionFactory();
   redisConnectionFactory.setHostName(hostName);
   redisConnectionFactory.setPort(port);
   return redisConnectionFactory;
}

Regarding Redis there was also change here:

From:

@Bean(name = PERSON_HISTORY_CACHE_MANAGER)
public CacheManager personHistoryCacheManager(@Qualifier("personTemplate")RedisTemplate personTemplate) {
    RedisCacheManager cacheManager = new RedisCacheManager(personTemplate);
    cacheManager.setDefaultExpiration(expirationValidityMinutes * 60); 
    return cacheManager;
}

To:

@Bean(name = PERSON_HISTORY_CACHE_MANAGER)
public CacheManager personHistoryCacheManager(RedisConnectionFactory redisConnectionFactory) {
    Duration expiration = Duration.ofSeconds(expirationValidityMinutes * 60);
    
    return RedisCacheManager
            .builder(redisConnectionFactory)
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().entryTtl(expiration))
            .build();
}

 

GuavaCacheManager to CaffeineCacheManager

GuavaCacheManager changed into CaffeineCacheManager which needs an additional library (if you want to configure CaffeineSpec like using guavaCacheManager.setCacheBuilder in the java file instead of using application properties):

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

 

CrudRepository methods

CrudRepistory changed its names of methods and also started to return Optional type by default.

The methods changed from operation on collections:

delete() -> deleteAll()

save() -> saveAll()

And also with finding giving now an Optional:

findOne() -> findById()

findById() return an Optional, because of that a good idea is to use orElseThrow() and throwing an exception if the element was not found.

 

Problem with inject

I had problems with inject which had been fixed by adding this dependency:

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>3.0</version>
</dependency> 

 

Datasource URL property

The property like:

primary.datasource.url=jdbc:postgresql://192.168.0.111:5432/someAdress?currentSchema=some_registries

changed to:

primary.datasource.jdbc-url=jdbc:postgresql://192.168.0.111:5432/someAdress?currentSchema=some_registries

Spring Boot 2.x.x changed default JDBC connection pool from Tomcat to faster and better HikariCP. Here comes incompatibility, because HikariCP uses different property of jdbc url.

 

Replace hibernate-validator dependency

Then I changed dependency from:

<dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>${hibernate-validator.version}</version>
</dependency> 

To:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency> 

I started getting this error while starting some modules:

2018-10-22 11:33:19.075 ERROR [moto,,,] 12260 --- [on(3)-127.0.0.1] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'documentationPluginsBootstrapper' defined in URL [jar:file:somePackages/someJar!/springfox/documentation/spring/web/plugins/DocumentationPluginsBootstrapper.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webMvcRequestHandlerProvider' defined in URL [jar:file:somePackages/someJar!/springfox/documentation/spring/web/plugins/WebMvcRequestHandlerProvider.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.ArrayStoreException

(...)

Caused by: java.lang.ArrayStoreException: null

I tried to change Swagger dependencies to new ones.

From:

<swagger.version>2.7.0</swagger.version>
<swagger-maven-plugin.version>3.1.5</swagger-maven-plugin.version>
<swagger2markup-maven-plugin.version>1.3.1</swagger2markup-maven-plugin.version> 

To:

<swagger.version>2.9.2</swagger.version>
<swagger-maven-plugin.version>3.1.7</swagger-maven-plugin.version> 

<swagger2markup-maven-plugin.version>1.3.7</swagger2markup-maven-plugin.version>

But it did not help. Suppression of Swagger related code led to changing cxf version:

From:

<properties>
    <cxf.version>3.1.11</cxf.version>
</properties> 

To:

<properties>
    <cxf.version>3.2.6</cxf.version>
</properties> 

Also Guava version changed from 18 to 21.

From:

<properties>
    <guava.version>18.0</guava.version>
</properties> 

To:

<properties>
    <guava.version>21.0</guava.version>
</properties> 

And that fixed this issue in one module. In the next one there was also an error saying:

nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.hibernate.validator.internal.engine.ConfigurationImpl

This was not obvious but I fixed it by changing dependency from:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
         </exclusion>
     </exclusions>
</dependency> 

To:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency> 

As we can see here – exclusions should be used with caution. The error even did not gave a hint of what was the main cause of it. The starter-type dependencies are made, so they work well together. If we remove one part, something can go wrong.

 

Remove migrator

I got rid of spring-boot-properties-migrator dependency, as I did not need it anymore.

 

Build successfully

After this step all modules were started and built successfully.
It was time to deploy them and see if there are some other issues.

 

Problem with JWT Token

While I was starting my tests, there was an issue with bearer token – when I was sending a request directly via service everything was OK but after I sent request indirectly via zuul then I always got 401, even though token was valid (the same one that was passing in a direct request).

This was fixed by changing these dependencies:

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>${spring-security-oauth2.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>${spring-security-jwt.version}</version>
</dependency> 

To:

  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
  </dependency>
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-security</artifactId>
  </dependency> 

 

Summary

I think that the migration process could be easier if there was more information about it. I could not find many answers to problems that I had to deal with regarding it.
I hope that this article is going to make someone’s life easier and will result in less headaches 😉

 

Author: Kamil Witkowski, Java Developer, ASC

Zarejestruj się na webinar:

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych dla potrzeb procesu realizacji zgłoszenia (pokaż więcej) przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa), zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”). Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne, ale konieczne do obsługi zgłoszenia oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych w celach marketingowych (pokaż więcej)przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa)oraz otrzymywanie informacji handlowych drogą elektroniczną na adres e-mail i numer telefonu podany przeze mnie, zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”), oraz Ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną. Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

 

Watch for free

Enter your contact details to receive a link to the recording:

I voluntarily consent to the processing of my personal data for the purposes of the application process (show more) by Altkom Experts sp. z o.o. and Altkom Software & Consulting sp. z o.o. (51 Chłodna Street, 00-867 Warsaw), in accordance with Regulation 2016/679 of the European Parliament and of the Council of 27 April 2016 on the protection of individuals with regard to the processing of personal data and on the free movement of such data ("RoDO"). I am aware that providing the above personal data is voluntary, but necessary to handle the request, and that I have the right to access the content of my data and the possibility of correcting it, and that my consent may be revoked at any time. Please contact the Altkom Data Protection Officer: iodo@altkom.pl.

I voluntarily consent to the processing of my personal data for marketing purposes (show more)by Altkom Experts sp. z o.o. and Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warsaw) and receiving commercial information by electronic means to the e-mail address and telephone number provided by me, in accordance with Regulation 2016/679 of the European Parliament and of the Council of 27 April 2016 on the protection of individuals with regard to the processing of personal data and on the free movement of such data ("RODO"), and the Act of 18 July 2002 on the provision of electronic services. I am aware that providing the above personal data is voluntary and that I have the right to access and correct my data and that my consent may be revoked at any time. Please contact the Altkom Data Protection Officer: iodo@altkom.pl.

 

Zarejestruj się na webinar:

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych dla potrzeb procesu realizacji zgłoszenia (pokaż więcej) przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa), zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”). Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne, ale konieczne do obsługi zgłoszenia oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych w celach marketingowych (pokaż więcej)przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa)oraz otrzymywanie informacji handlowych drogą elektroniczną na adres e-mail i numer telefonu podany przeze mnie, zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”), oraz Ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną. Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

 

Zarejestruj się na webinar:

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych dla potrzeb procesu realizacji zgłoszenia (pokaż więcej) przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa), zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”). Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne, ale konieczne do obsługi zgłoszenia oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych w celach marketingowych (pokaż więcej)przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa)oraz otrzymywanie informacji handlowych drogą elektroniczną na adres e-mail i numer telefonu podany przeze mnie, zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”), oraz Ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną. Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

 

Zarejestruj się na webinar:

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych dla potrzeb procesu realizacji zgłoszenia (pokaż więcej) przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa), zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”). Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne, ale konieczne do obsługi zgłoszenia oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych w celach marketingowych (pokaż więcej)przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa)oraz otrzymywanie informacji handlowych drogą elektroniczną na adres e-mail i numer telefonu podany przeze mnie, zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”), oraz Ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną. Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl