0% found this document useful (0 votes)
12 views13 pages

SpringBoot Migration Java8 To Java17 or 21

The document outlines the migration process from Spring Boot 2.x with Java 8 to Spring Boot 4.0.2 with Java 17 or 21, highlighting key changes such as the transition from javax to jakarta packages, Hibernate upgrades, and modifications in Spring Security configurations. It details necessary code changes, including updates to exception handling and native query handling to avoid issues with duplicate SQL aliases. Additionally, it emphasizes the importance of reviewing native JOIN queries and adapting Hibernate dialect configurations due to changes in Hibernate 6.

Uploaded by

xepif75442
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views13 pages

SpringBoot Migration Java8 To Java17 or 21

The document outlines the migration process from Spring Boot 2.x with Java 8 to Spring Boot 4.0.2 with Java 17 or 21, highlighting key changes such as the transition from javax to jakarta packages, Hibernate upgrades, and modifications in Spring Security configurations. It details necessary code changes, including updates to exception handling and native query handling to avoid issues with duplicate SQL aliases. Additionally, it emphasizes the importance of reviewing native JOIN queries and adapting Hibernate dialect configurations due to changes in Hibernate 6.

Uploaded by

xepif75442
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

s

The detailed summary of the migration from Spring Boot 2.x with Java 8 to Spring Boot 4.0.2 with Java
17 or 21.
When upgrading from Spring Boot 2.x to Spring Boot 3.x or 4.0.2 generation + Java 21, the following
areas must be handled:
1. Java version upgrade
2. javax package to jakarta package
3. Hibernate 5 → Hibernate 6
4. Spring Security configuration changes
5. CsrfTokenRequestAttributeHandler: Spring Boot 3.x vs 2.x
6. Swagger replacement
7. Removed deprecated APIs
8. Third-party dependency compatibility
9. All native JOIN queries were reviewed during migration to prevent duplicate alias conflicts
10. ResponseEntityExceptionHandler override method signatures changed
11. Hibernate dialect changes between Spring Boot versions

Upgrade JDK 1.8 to JDK 21 Changes


Update Maven/Gradle ([Link] file changes)

Java 8 + Spring Boot 2.x Java 17 or Java 21 + Spring Boot 3.x/4.x

<properties> <properties>
<[Link]>1.8</[Link]> <[Link]>21</[Link]>
</properties> </properties>

<dependency> <dependency>
<groupId>[Link]</groupId> <groupId>[Link]</groupId>
<artifactId>[Link]-api</artifactId> <artifactId>[Link]-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>

Code Changes
This is one of the most important changes when upgrading from Java 8 + Spring Boot 2.x to Java 17 or
Java 21 + Spring Boot 3.x/4.x
2.1) You must change:
[Link].* package to➝ [Link].* package
s

Java 8 + Spring Boot 2.x Java 17 or Java 21 + Spring Boot 3.x/4.x

import [Link].* import [Link].*

import [Link]; import [Link];


public class User { public class User {
@NotBlank(message =”userId must not be blank”) @NotBlank(message =”userId must not be
private String userId; blank”)
//setters and getters private String userId;
} //setters and getters
}

2.2) You must change:


[Link].* ➝ [Link].*

Java 8 + Spring Boot 2.x Java 17 or Java 21 + Spring Boot 3.x/4.x

[Link].* [Link].*

import [Link]; import [Link];


import [Link]; import [Link];
import [Link]; import [Link];
import [Link]; import [Link];
import [Link]; import [Link];

@Entity @Entity
@Table(name = "users") @Table(name = "users")
public class Users { public class Users {

@Id @Id
@GeneratedValue(strategy = @GeneratedValue(strategy =
[Link]) [Link])
private Long id; private Long id;
//setter and getters //setter and getters
} }

Annotation New (jakarta)


@Entity [Link]
@Table [Link]
@Id [Link]
s

@Column [Link]
@OneToMany [Link]
@ManyToOne [Link]
@JoinColumn [Link]
@Transient [Link]
@Enumerated [Link]

2.3) Security Configuration Migration Code Changes


Area Java 8 + Spring Boot 2.x Java 17 or Java 21 + Spring Boot 3.x/4.x
Authorize method authorizeRequests() authorizeHttpRequests()
URL matchers antMatchers() requestMatchers()
CSRF ignoring ignoringAntMatchers() ignoringRequestMatchers()
Headers config .headers().xssProtection() Lambda style config
Chaining style .and() chaining Lambda DSL
Security Adapter Extended class earlier SecurityFilterChain bean only

Java 8 + Spring Boot 2.x (OLD)

package [Link];

import [Link];

import [Link];
import [Link];
import
[Link];
import [Link];
import
[Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled =true)
public class SecurityConfig {
s

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
CustomCSRFRepository csrfTokenRepository = [Link]();
[Link](true);
[Link]("/");

http
.headers()
.xssProtection().disable()
.contentSecurityPolicy("script-src 'self'")
.and()
.addHeaderWriter(new StaticHeadersWriter(
"Expect-CT", "max-age=31536000, enforce"))
.and()

.csrf()
.csrfTokenRepository(csrfTokenRepository)
.ignoringAntMatchers("/V1/token")
.and()

.cors()
.configurationSource(corsConfigurationSource())
.and()

.sessionManagement()
.sessionCreationPolicy([Link])
.and()

.authorizeRequests()
.antMatchers("/V1 /token").permitAll()
.antMatchers("/role").authenticated()
.antMatchers("/country/**").hasAuthority("/V1/businessusers/**")
.antMatchers("/nonbusinessadmin/**").hasAnyAuthority("/V1/admin/role/**")
.anyRequest().authenticated();

[Link](jwtFilter(),
[Link]);
}

// CORS Configuration
@Bean
public CorsConfigurationSource corsConfigurationSource() {

CorsConfiguration configuration = new CorsConfiguration();


[Link]([Link]("*"));
[Link](
[Link]("GET", "POST", "PUT", "DELETE", "OPTIONS"));
s

[Link]([Link]("*"));
[Link](true);

UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
[Link]("/**", configuration);

return source;
}

// JWT Filter Bean


@Bean
public JwtFilter jwtFilter() {
return new JwtFilter();
}
}

Java 17 or Java 21 + Spring Boot 3.x/4.x (New)

package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig {

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

CsrfTokenRequestAttributeHandler csrfHandler = new CsrfTokenRequestAttributeHandler();


http
.headers(headers -> headers
.xssProtection(xss -> [Link]())
.contentSecurityPolicy(csp ->
s

[Link]("script-src 'self'"))
.addHeaderWriter(new StaticHeadersWriter(
"Expect-CT", "max-age=31536000, enforce"))
)

.csrf(csrf -> csrf


.csrfTokenRequestHandler(csrfHandler).csrfTokenRepository(csrfTokenRepository)
.ignoringRequestMatchers("/V1/token")
)

.cors(cors -> cors


.configurationSource(corsConfigurationSource())
)

.sessionManagement(session ->
[Link]([Link])
)

.authorizeHttpRequests(auth -> auth


.requestMatchers("/V1 /token").permitAll()
.requestMatchers("/role").authenticated()
.requestMatchers("/country/**").hasAuthority("/V1/businessusers/**")
.requestMatchers("/nonbusinessadmin/**")
.hasAnyAuthority("/V1/admin/role/**")
.anyRequest().authenticated()
)

.addFilterBefore(jwtFilter(),
[Link]);

return [Link]();
}

// CSRF Repository Bean


@Bean
public CustomCSRFRepository csrfTokenRepository() {
CustomCSRFRepository repo =
[Link]();
[Link](true);
[Link]("/");
return repo;
}

// CORS Configuration
@Bean
public CorsConfigurationSource corsConfigurationSource() {

CorsConfiguration configuration = new CorsConfiguration();


s

[Link]([Link]("*"));
[Link](
[Link]("GET", "POST", "PUT", "DELETE", "OPTIONS"));
[Link]([Link]("*"));
[Link](true);

UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
[Link]("/**", configuration);

return source;
}

CsrfTokenRequestAttributeHandler: Spring Boot 3.x vs 2.x

• Spring Boot 2.x + Spring Security 5 (Java 8)

o CSRF tokens were automatically exposed via _csrf request attribute.

o Templates (e.g., Thymeleaf) could directly use ${_csrf.token}.

o No extra configuration was needed; Spring Security handled token exposure.

• Spring Boot 3.x / 4.x + Spring Security 6 (Java 17/21)

o CSRF handling was refactored; tokens are not automatically exposed.

o CsrfTokenRequestAttributeHandler is required to map the token to request attributes:

• CsrfTokenRequestAttributeHandler csrfHandler = new CsrfTokenRequestAttributeHandler();

.csrf(csrf -> csrf


.csrfTokenRequestHandler(csrfHandler).csrfTokenRepository(csrfTokenRepository)
.ignoringRequestMatchers("/V1/token")
)
o Explicit token exposure improves control and security.

o Ensures compatibility with REST APIs, Thymeleaf forms, and AJAX requests.

o Aligns with the Lambda-based configuration style in Spring Security 6.

2.4) As part of the migration from Java 8 + Spring Boot 2.x to Java 21 + Spring Boot 3.x/4.x, we
encountered an issue related to native queries with JOIN operations.

Issue Description

Java 8 + Spring Boot 2.x (Working)


s

@Query(value = "SELECT * FROM [Link] o LEFT JOIN [Link] c


ON o.customer_id = c.customer_id WHERE [Link] = :status", nativeQuery = true)
List<Order> findOrders(@Param("status") String status);

This query worked correctly in Spring Boot 2.x (Hibernate 5).

Issue in Java 21 + Spring Boot 3.x / 4.x

The same query now throws the following error:

Error: Encountered a duplicate SQL alias 'customer_id' during auto-discovery

Why This Happens

Both tables contain the same column:

• o.customer_id

• c.customer_id

When using SELECT *, Hibernate tries to automatically map the result set to the Order entity.

However, since both tables return a column named customer_id, Hibernate 6 detects duplicate aliases
and cannot determine which column belongs to the mapped entity.

Hibernate 6 (used in Spring Boot 3+) is stricter than Hibernate 5.


That’s why the query worked earlier but now fails.

Root Cause

The result set contains two customer_id columns:

o.customer_id

c.customer_id

This causes:

Duplicate SQL alias → Mapping ambiguity → Runtime failure

Correct Solution

Never use SELECT * in a native query with JOIN.

Instead, explicitly select only the columns of the entity you are mapping.

If the return type is List<Order>, use:

@Query(value = "SELECT o.* FROM [Link] o LEFT JOIN [Link] c


ON o.customer_id = c.customer_id WHERE [Link] = :status", nativeQuery = true)
s

List<Order> findOrders(@Param("status") String status);

Why This Fix Works

• Hibernate now maps only o (orders) columns

• No duplicate alias

• Clean and safe

• Fully compatible with Hibernate 6

Please ensure all native queries with JOINs are reviewed during migration to avoid similar alias conflicts.

2.5) Exception Handler

When moving Java 8 + Spring Boot to Java 21 + Spring Boot / 4.x

the ResponseEntityExceptionHandler override method signatures changed.

What Changed in Spring Boot 3 / 4?

HttpStatus ➝ HttpStatusCode

ResponseEntityExceptionHandler Override methods:

handleMethodArgumentNotValid

Spring Boot 2.x (OLD)

@Override

protected ResponseEntity<Object> handleMethodArgumentNotValid(

MethodArgumentNotValidException ex, HttpHeaders headers,

HttpStatus status, WebRequest request)

Spring Boot 3.x / 4.x (NEW)

@Override

protected ResponseEntity<Object> handleMethodArgumentNotValid(

MethodArgumentNotValidException ex, HttpHeaders headers,

HttpStatusCode status, WebRequest request)


s

handleHttpMessageNotReadable

Spring Boot 2.x

@Override

protected ResponseEntity<Object> handleHttpMessageNotReadable(

HttpMessageNotReadableException ex,HttpHeaders headers,

HttpStatus status, WebRequest request)

Spring Boot 3.x / 4.x (NEW)

@Override

protected ResponseEntity<Object> handleHttpMessageNotReadable(

HttpMessageNotReadableException ex, HttpHeaders headers,

HttpStatusCode status, WebRequest request)

handleMissingServletRequestParameter

OLD

@Override

protected ResponseEntity<Object> handleMissingServletRequestParameter(

MissingServletRequestParameterException ex, HttpHeaders headers,

HttpStatus status, WebRequest request)

NEW

@Override

protected ResponseEntity<Object> handleMissingServletRequestParameter(

MissingServletRequestParameterException ex, HttpHeaders headers,

HttpStatusCode status, WebRequest request)

Why This Changed?


s

Spring Framework 6 introduced: HttpStatusCodeInstead of tightly coupling everything to HttpStatus


[Link] gives more flexibility for custom status codes.

Fully Correct Boot 3 / 4 Example

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatusCode status, WebRequest request) {
Map<String, String> errors = new HashMap<>();
[Link]().getFieldErrors()
.forEach(error -> [Link]([Link](), [Link]()) );
return [Link](status).body(errors);
}
}

2.6) Using @GeneratedValue(strategy = [Link]) without an IDENTITY column or


SEQUENCE works in Spring Boot 2.x because Hibernate 5 often defaulted to IDENTITY for SQL Server.
In Spring Boot 3.x / 4.x, Hibernate 6 prefers SEQUENCE if the database supports it.
Since SQL Server (2012+) supports SEQUENCE, Hibernate now attempts to use one.
But no SEQUENCE exists in the DB and the column is not IDENTITY.
So Hibernate tries select next value for hibernate_sequence and the insert fails.
Fix: Explicitly use [Link] with an IDENTITY column or define a proper SEQUENCE and
avoid AUTO in Boot 3/4.

2.7) Hibernate dialect changes between Spring Boot versions.


Spring Boot 2.x uses Hibernate 5.x.
Hibernate 5 supported the following dialect configurations in [Link]:
[Link]=[Link]
[Link]=[Link].SQLServer2012Dialect
[Link]=[Link].SQLServer2008Dialect
Therefore, in Spring Boot 2.x:
s

SQLServer2012Dialect works perfectly fine.


Spring Boot 3.x and 4.x
Spring Boot 3.x and 4.x use:
• Hibernate 6
Hibernate 6 removed version-specific dialect classes such as:

SQLServer2012Dialect
SQLServer2008Dialect
Hibernate 6 simplified the dialect strategy. Now the correct configuration is:
[Link]=[Link]
Error Observed When Using Old Dialect in Boot 3.x / 4.x
If SQLServer2012Dialect is still configured, the following error occurs:
WARN: HHH1000046: Could not obtain connection to query JDBC database metadata
[Link]:
Unable to resolve name [[Link].SQLServer2012Dialect]
as strategy [[Link]]
at [Link]
at [Link]
This happens because the class no longer exists in Hibernate 6.
For Spring Boot 3.x / 4.x applications, please ensure the dialect is updated to:
[Link]=[Link]

2.8) Swagger replacement to Springdoc OpenAPI

Swagger has not been removed in Spring Boot 4.x. The issue arises because Springfox (the older Swagger
integration library) is not compatible with Spring Boot 3.x and 4.x.
Spring Boot 2.x was based on Java 8, javax.* packages, and Hibernate 5. Since Springfox was built using
javax.*, it worked without issues in Boot 2.x.
However, Spring Boot 3+ migrated from javax.* to jakarta.*, and Spring Boot 4.x is fully aligned with
Jakarta EE 10, Hibernate 6, and Spring Framework 6+. As Springfox was never upgraded to properly
support Jakarta, it fails in Boot 3.x/4.x environments.
Common errors include:
• ClassNotFoundException: [Link].*
s

• Swagger UI not loading


• Bean creation failures during startup
Since Springfox is no longer actively maintained, it is not suitable for modern Spring Boot versions.
The recommended replacement is Springdoc OpenAPI, which fully supports Spring Boot 3.x/4.x and
Jakarta packages and is actively maintained.

You might also like