Step by Step Migration to Spring 3.x (Jakarta,Security,Hibernate 6, OpenApi,Cache)

Serdar A.
6 min readApr 19, 2023

--

Hello everyone.

For Turkish : https://lastjavabuilder.medium.com/projeyi-ad%C4%B1m-ad%C4%B1m-spring-boot-3-xe-g%C3%BCncelleme-jakarta-security-hibernate-6-openapi-cache-75d4d4cc8d8e

In this article, I will try to explain my experience in migration the Spring Boot 2.x project that I have been working on recently to Spring Boot 3.x.

After reading you can visit part-2 (https://medium.com/@lastjavabuilder/part-2-step-by-step-migration-to-spring-security-6-2-x-16fac83f45db)

With Spring 3.x, there have been radical changes to the existing Spring infrastructure.Spring Framework 6 support came with Spring 3.x.And also, if you check the Spring documentation, ((https://spring.io/blog/2022/05/24/preparing-for-spring-boot-3-0)) Java 17 support has arrived. If you want to use Spring 3.x, the Java version in the project must be at least 17.

One of the most radical changes with Spring 3.x is the replacement of Java EE 8 (javax.*) with the Jakarta EE 9 API (jakarta.*) library.

It is recommended to use Hibernate 6 with Spring 3.x. With Hibernate 6, there have been some radical changes, which I will go into detail in a moment.

In addition, with Spring 3.x, new dependencies on OpenApi need to be included in the project. (https://springdoc.org/v2/)

In this article, I will share both the improvements on how to move the project from Spring 2.x to Spring 3.x and what kind of problems I encountered.

Let’s start :)

  1. First of all, it is necessary to remove the old dependencies and define the new dependencies in pom.xml (if you are using Maven).

We need to update the spring-boot-starter-parent dependency version to 3.x in pom.xml. As an example, version 3.0.2 is used.

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath/>
</parent>

2. If you are using spring-boot-maven-plugin in your project’s pom.xml file, we need to update its version to 3.x.

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>

Note: If the modules (web, security etc) of spring in your project do not get the version information from the parent, and you have defined separately, you should update the version information of these modules to a minimum of 6.x in order for these modules to be compatible with Spring 3.x.

For example

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>6.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>6.0.1</version>
</dependency>

3. As I mentioned above, you need to update the Java version of your project to a minimum of 17.

4. When you make maven clean install or maven update after making changes in pom.xml, you will see many errors in your codes. Now let’s start solving these errors step by step.

5.You will see that many lines of code that get errors are where the javax.* packages are imported. With Spring 3.x, jakarta.* libs came instead of javax.* libs.

6.To solve these errors, we first need to add the following two dependencies to our pom.xml.

<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>

7.We need to change all javax.* import definitions in our project to jakarta.* import.

For example, we need to remove the following definitions

//Remove this codes
//import javax.persistence.CascadeType;
//import javax.persistence.Column;
//import javax.persistence.Convert;
//import javax.persistence.Entity;
//import javax.persistence.FetchType;
//import javax.persistence.Index;
//import javax.persistence.JoinColumn;
//import javax.persistence.OneToMany;
//import javax.persistence.Table;
//import javax.persistence.Transient
//import javax.persistence.criteria.CriteriaBuilder;
//import javax.persistence.criteria.CriteriaQuery;
//import javax.persistence.criteria.Join;
//import javax.persistence.criteria.Root;
//import javax.persistence.criteria.Subquery;
//import javax.persistence.LockModeType;
//import javax.persistence.QueryHint;
//import javax.persistence.AttributeConverter;
//import javax.persistence.PostPersist;
//import javax.persistence.PostRemove;
//import javax.persistence.PostUpdate;

//import javax.validation.constraints.NotNull;
//import javax.transaction.Transactional;

//javax.persistence.criteria.Predicate

Add this code blocks

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.LockModeType;
import jakarta.persistence.QueryHint;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;

import jakarta.validation.constraints.NotNull;
import jakarta.transaction.Transactional;

import jakarta.persistence.criteria.Predicate

8. If you are using Hibernate in your project, it is recommended to use Hibernate minimum 6.x version together with Spring 3.x. For this we have to update the versions of Hibernate dependencies to 6.x.

For example, for hibernate-core

<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.0.CR2</version>
</dependency>

add this dependency.

9. If your project has Hibernate’s definitions such as @TypeDef and @Type, these definitions are not used with Hibernate 6.x. You can use the @Converter annotation that comes with the Jakarta Persistent API instead.

For example, in definitions before Hibernate 6.x

import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;

@TypeDef(name = "json", typeClass = StringJsonUserType.class)
public class EntityName {

@Type(type = "json")
private propertyName

With Spring 3.x and Jakarta, we can make a definition as follows.

import jakarta.persistence.Convert;
import org.hibernate.annotations.JdbcTypeCode;


@Convert(attributeName = "entityAttrName", converter = StringJsonUserType.class)
public class EntityName {


@JdbcTypeCode(SqlTypes.JSON)
private propertyName

Note: You can make any definition you want for the entityAttrName parameter.

10. With Hibernate 6.x, L2 Cache definitions have been changed. The factory class definition defined in application.properties for Ecache starts to give an error. As a solution to this, you can solve this problem by using a JCache implementation with Ecache.

Old definition,

dependency

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.6.12.Final</version>
</dependency>

code

@javax.persistence.Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EntityName
{
...
}

application.prop

spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory

In new definition,

dependency

<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>6.0.0.Alpha7</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jcache</artifactId>
<version>6.2.0.CR2</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>

code

@jakarta.persistence.Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EntityName {

....

}

application.prop

spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=jcache
spring.jpa.hibernate.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider

11. If you are using the Hibernate UserType in your project, you may also be getting errors in the lines of code here. As a solution to this;

Old definition,

public class CustomUserType implements UserType {

@Override
public int[] sqlTypes() {
return new int[]{Types.JAVA_OBJECT};
}

@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {

.....
}

....
}

In new definition,

public class CustomUserType implements UserType {

@Override
public int getSqlType() {
return Types.JAVA_OBJECT;
}

@Override
public Object nullSafeGet(ResultSet resultSet, int i, SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws SQLException {

....
}

}

12. With Spring 3.x, OpenAPI dependencies have also changed. (https://springdoc.org/v2/)

First you need to remove old dependencies.

Old dependency definition

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.13</version>
</dependency>

New dependency definition

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.0.2</version>
</dependency>

13. One of the faulty situations I encountered with Spring 3.x is that some definitions in application.properties or application.yml have changed.

If you are using profile definitions in your project;

Old prop definition;

spring:
profiles: "dev"

--
spring:
profiles: "test"

New prop definition;

spring:
config:
activate:
on-profile: "dev"


spring:
config:
activate:
on-profile: "test"

If you are defining a List in properties or yaml. Here is a situation where I got an error. To resolve this error situation;

Old prop definition;

service:
uris: >
http://x.y.z.1:121,
http://x.y.z.2:122,
http://x.y.z.3:123

New prop definition;

service:
uris:
- 'http://x.y.z.1:121’
- 'http://x.y.z.2:122’
- 'http://x.y.z.3:123’'

14. With Spring 3.x, there have been some radical changes in security definitions. The adapter structure has been completely removed. In the previous version, configuration definitions were made by extending the WebSecurityConfigurerAdapter class, but with Spring 3.x, the WebSecurityConfigurerAdapter class structure was removed.Instead, you can configure the filterChain Bean definition.

Old code;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//code

@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService);
}

@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/v3/api-docs/**")
.antMatchers("/swagger-resources/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/swagger-ui.html")
.antMatchers("/service")
.antMatchers("/actuator/**");
}


@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/checkService")
.permitAll()
.anyRequest().authenticated()
.and()
.userDetailsService(userDetailsService)
.addFilter(
new JWTAuthorizationFilter(this.authenticationManager(),filterService)
)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

New code;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private static final String[] AUTH_WHITELIST = {
"/v3/api-docs/**",
"/swagger-resources/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/service ",
"/actuator/**",
"/checkService "
};

@Bean
public SecurityFilterChain filterChain(HttpSecurity http,AuthenticationManager authnManager) throws Exception {


http.
cors().configurationSource(corsConfigurationSource())
.and()
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(AUTH_WHITELIST)
.permitAll()
.anyRequest()
.authenticated())
.authenticationManager(authnManager)
.addFilter(new JWTAuthorizationFilter(authnManager, filterService))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();




}

private CorsConfigurationSource corsConfigurationSource() {


List<String> list = new ArrayList<>();
list.add("*");

final var corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedOrigins(Arrays.stream(corsPropList.getOrigins()).toList());
corsConfiguration.setAllowedHeaders(list);
corsConfiguration.setAllowedMethods(list);

final var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);

return source;
}
}

With the filterChain Bean definition, many configurations can be made from this Bean.

15. If you are using spring-security in your project, instead of removing the old dependency for OpenAPI Spring 3.x, you need to add the springdoc-openapi-security dependency.

Old dependency definition

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>1.5.13</version>
</dependency>

New dependency definition

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>1.6.4</version>
</dependency>

These are the situations I encountered and the actions I took during this update for now. If I have other problems from time to time, I can update as part-2.

Thank you for reading.

--

--

Serdar A.

Senior Software Developer & Architect at Havelsan Github: https://github.com/serdaralkancode #Java & #Spring & #BigData & #React & #ReactNative & #JS #Microserv