Java 21 Migration Guide
Overview
This document is designed to be a comprehensive resource for users who have deployed the latest versions of the Modular Open Source Identity Platform (MOSIP) compatible with Java 11 and are preparing to upgrade their systems to Java 21. It provides a detailed, step-by-step migration process to facilitate a seamless and efficient transition. By adhering to the guidelines in this document, users can modernize their MOSIP environments to take full advantage of Java 21's improved performance, advanced security features, and enhanced functionality. The guide also emphasizes best practices to minimize disruptions, maintain system stability, and ensure compliance throughout the upgrade process.
1. Pre-requisites:
JDK 21: Ensure Java Development Kit (JDK) 21 is installed and configured in your system's environment variables.
Maven (Latest Version): To build and manage dependencies, use the latest version of Maven, such as 3.9.6.
Optional:
A modern IDE (e.g., Eclipse, IntelliJ IDEA, or others) is compatible with Java 21 to streamline coding and debugging.
Ensure the Lombok library version is compatible with your IDE. For instance, Lombok 1.18.30 works seamlessly with the latest IDE versions.
Note: After adding Lombok to your project, ensure it is correctly set up in your IDE to avoid compilation issues. This typically involves running the Lombok installer or manually enabling it in the IDE settings.
2. Backward compatibility:
Java applications compiled in older versions are compatible with Java 21 run time. To support running those application jars in Java 21, additional VM Arguments need to be added while running applications in Java 21.
Java applications compiled with older versions are compatible with the Java 21 runtime. However, to ensure these application JARs run correctly in Java 21, additional JVM arguments may need to be specified when running the applications.
The libraries must be API-compatible and should not rely on deprecated or removed APIs.
The libraries should not depend on older Spring Boot versions (before 3.x), as the newer Spring Boot versions introduce significant API changes. Failure to meet this requirement can lead to compile-time or runtime issues, such as errors during class loading, bean initialization, or method invocation.
The dependent libraries of any module that have a dependency on any other MOSIP library (such as kernel-core) or older Spring Boot version (older than 3.x) need to be migrated before migrating the specific module. This applies not only to the static dependencies mentioned in the POM file, but also to the dynamic dependencies loaded from the classpath such as Kernel Auth Adaptor, BioSDK client, or any such libraries.
3. Migration Changes in a module/repository:
I. POM file changes:
All POM versions of the modules and their dependency modules should be updated to reference the Java 21 migrated version.
Change the source and target compiler versions to 21:
Jacoco-plugin version needs to be updated to 0.8.11:
Note: A new kernel-bom file has been introduced as part of this release in the commons repo which contains all the latest version changes to the spring-boot and other dependencies. Here spring-boot:3.2.3 is used.
Please refer to this file to learn about the dependencies and versions. this file is created to remove the repetitiveness in defining the dependencies. If you have other repositories that use repeated definitions of dependencies, create a new bom file for that specific repository that includes the kernel-bom in the dependencyManagement section then add the extra dependencies with appropriate versions, and then use the same bom file in your respective modules pom files.
Any module that needs the predefined versions for any of its dependencies should import the kernel-bom file into the module pom file’s dependency management section. Remove all the versions from the properties and dependencies section for which kernel-bom has already defined the version. Please refer to the example in the audit-service pom file. (You will need to include the latest link once tagging is done). If a module does not need any version from the kernel-bom, it's not needed to import it.
A module pom or bom file can import one or more bom files, and if there is a dependency referred from more than one bom file, it will be referred from the last bom file. Please refer here.
Unless there is a compelling reason for using a different version of the library than the version defined in kernel-bom, do not mention the version to that dependency, if done it will override the version with the specified one.
Remove any unused version properties from the pom.
Swagger UI update: To upgrade to Swagger-UI version 3, make the following changes: 1. Update the Dependency Add the
springdoc-openapi-starter-webmvc-ui
dependency to your pom.xml file.
2. Remove Deprecated Dependency
Remove any reference to the springdoc-openapi-ui
dependency to prevent conflicts.
Note: If swagger-2 was used in the module already, change it to Swagger-3 and also make the above change.
Bouncy Castle Version update: Remove reference to an older version of the bouncycastle library and use the below one. The same is applied in kernel-bom and kernel-core. Please refer to the pom.xml file.
Note:
Any exclusions specified for a library in the POM can be retained. However, the version mentioned in that dependency can be removed, allowing it to inherit the version defined in the kernel-bom or another POM file.
Always make it a practice to keep the versions in the properties instead of hardcoding.
Even if the version is changed in the properties of pom.xml, make sure those properties are used in those dependencies/plugins instead of hardcoding the version.
For example:
maven-javadoc-plugin dependency should refer to ${maven.javadoc.version} property in the POM file.
Check and remove any unused version properties. With the use of kernel-bom, the version does not need to be mentioned to the dependencies mostly, unless it needs to be overridden or a different version is used.
POM files should not include duplicate version properties, as this can lead to errors. For example, even if the version property is updated correctly in its first occurrence, subsequent occurrences may override it with an outdated or incorrect version. This behavior can go unnoticed and may cause unexpected errors or functionality issues. To avoid such problems, carefully review POM files to identify and remove any repeated version properties. This ensures consistent version management and prevents overriding conflicts.
II. Java file changes:
Below are the package changes that need to be applied in Java files.
javax.servlet.*
jakarta.servlet.*,
javax.annotation.*
jakarta.annotation.*,
javax.activation.*
jakarta.activation.*,
javax.persistence.*
jakarta.persistence.*,
javax.validation.*
jakarta.validation.*,
javax.mail.*
jakarta.mail.*,
org.apache.http.impl.client.*
org.apache.hc.client5.http.impl.classic.*,
org.apache.http.conn.ssl.SSLConnectionSocketFactory
org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory
Note: While doing a text replacement for the above packages, it might be accidentally renaming the properties having the same naming for example javax.persistence.jdbc.driver. Please make sure this does not happen. Please refer here to such fixes.
Postgres Hibernate Dialect: Instead of using specific version dialects for PostgreSQL, such as org.hibernate.dialect.PostgreSQL95Dialect or org.hibernate.dialect.PostgreSQL92Dialect, it should be org.hibernate.dialect.PostgreSQLDialect. This is applicable for properties referred to in Java code and any properties file as well.
The Sleuth configuration has now been migrated to the Micrometer Tracer
This required the addition of micrometer tracing dependencies and quartz scheduler dependency which is already included in kernel-core pom file.
Code changes were also necessary for the migration.
Import
BraveAutoConfiguration.class
instead of using a component scan oforg.springframework.cloud.sleuth.autoconfig.*
Use the
AccessLogValve
class as the base class instead ofValveBase
for theSleuthValve
class.Replace the following code:
tracer.newTrace()
→tracer.nextSpan()
span.context().traceIdString()
→span.context().traceId()
span.context().spanIdString()
→span.context().spanId()
Refer to the details below for the changes made.
HTTP Connection Manager changes:
The following changes have been introduced related to the HTTP Connection Manager:
Deprecation of Existing Configuration Methods
The previously existing configuration methods have been deprecated and removed.
New configuration methods must be used, utilizing updated classes such as:
ReactorLoadBalancerExchangeFilterFunction
PoolingHttpClientConnectionManagerBuilder
Direct Auto-Wiring of
ReactorLoadBalancerExchangeFilterFunction
Instead of using
LoadBalancerClient
withLoadBalancerExchangeFilterFunction
, directly auto-wireReactorLoadBalancerExchangeFilterFunction
in your implementation.
New Way to Set
setMaxConnPerRoute
The method for setting
setMaxConnPerRoute
in the HTTP connection manager now usesPoolingHttpClientConnectionManagerBuilder
.
For detailed implementation and examples, refer to this pull request.
Spring Security Changes: The Old way of extending WebSecurityConfigurationAdapter for SecurityConfig is deprecated, now it requires a new way of configuring the same using as mentioned below.
Remove
extends WebSecurityConfigurerAdapter
Replace
@EnableGlobalMethodSecurity(prePostEnabled = true)
with@EnableMethodSecurity
Replace the
.antMatchers()
method with.requestMatchers()
Replace the
@Override
on the configure method with@Bean
that returns theSecurityFilterChain
instance as a result ofhttp.build()
Replace the existing deprecated way of HttpSecurity configurations with lambda based configuration. Please refer to this file present here.
HttpStatusCodeException.getStatusCode()
should be replaced withHttpStatus.valueOf(statusCodeException.getStatusCode().value())
Please refer to this link where all above stated changes have been made:
The existing
bootstrap.properties
file continues to function only if the spring-cloud-starter-bootstrap dependency is added. This dependency is currently included in the kernel-core mentioned in the pom.xml file. If the dependency is not added, you will need to use application.properties instead.Spring Batch Migration: Spring Batch has been migrated to version 5.x. The following changes need to be applied:
DB Changes:
The Map-based job repository is now deprecated. Therefore, any application running a batch job must have a database with Batch Job-related tables. If a database is not feasible, at least an in-memory database must be configured. Refer to the DB script for creating the tables.
If a Spring datasource is already being used in the Spring Batch job application, the Batch Job-related tables must be created in that datasource (i.e., database) using the aforementioned DB script.
The existing Spring Batch Job tables have to be applied with the below upgrade script:
The rollback script for the above is given below:
POM Changes:
Add
spring-boot-starter-batch
if it does not already exist. The version will be 3.x ifkernel-bom
is used, as discussed in the previous sections.Add hibernate-validate dependency if it does not exist. The version will be 8.x if kernel-bom is used as discussed in the previous sections.
Java Code changes:
Remove
@EnableBatchProcessing
AnnotationThe
BatchConfigurer
andDefaultBatchConfigurer
classes are deprecated, and any references to them must be removed. For example, in thekernel-salt-generator
, these classes were used to implement a Map-based Job Repository. However, since the Map-based Job Repository is no longer supported and requires the use of database tables, the only option is to remove references to these classes and create the necessary batch job tables.Create a bean definition for
PlatformTransactionManager
Please refer to the SaltGeneratorConfig.java file. Below is the relevant code snippet from the file:JobBuilderFactory
andStepBuilderFactory
are deprecated. Instead, useJobBuilder
andStepBuilder
.For reference, please refer to the SaltGeneratorJobConfig.java file.
Since
JobExecutionListenerSupport
is deprecated in favor of theJobExecutionListener
interface, update the Batch Job Listener class to implementJobExecutionListener
instead of extendingJobExecutionListenerSupport
.For reference, see the relevant code in the BatchJobListener.java file.
The
write
method parameter in theorg.springframework.batch.item.ItemWriter
interface has been changed fromList
toChunk
.For reference, please refer to the SaltWriter.java file.
Key Changes:
To obtain a stream from a
Chunk
, use theStreamSupport
utility class as follows:Similar to the
List.of()
the method used for creating aList
, you can use theChunk.of()
method to create aChunk
.
References:
Spring Batch 5.0 Migration Guide: Spring Batch 5.0 Migration Guide
In
@RestControllerAdvice
, if the response content type is not explicitly set toapplication/json
, it might default to returning an XML response. To prevent this, ensure thecontentType
is specified in theResponseEntity
as shown below:
In
org.apache.commons.lang3.time.DateUtils
, null parameters will now throw aNullPointerException
instead of anIllegalArgumentException
. To prevent breaking functionality, handle the exception as shown below.The impact is observed in utility methods for the following classes:
You can handle the
NullPointerException
appropriately by adding a null check or by using a try-catch block.In the
org.apache.commons.lang3.StringUtils.join
method, invalid arguments now throw anIllegalArgumentException
instead of anArrayIndexOutOfBoundsException
.To prevent breaking functionality, handle this exception as shown in the StringUtils.java file.Symmetric Algorithm AES/GCM/PKCS5Padding is no longer supported and it should be replaced with AES/GCM/NoPadding. Please refer to the CryptoUtil changes mentioned in the CryptoUtil.java file.
In a JPA repository, for non-native query methods, the column names should be based on the entity’s field names rather than the actual column names. For example, if an entity has a column
lang_code
mapped to a fieldprivate String langCode
, the non-native query should uselangCode
instead oflang_code
.Since Joda Time is deprecated, the related date formatting classes have been removed from
spring-context
, which may cause compatibility issues. Therefore, replace Joda's Time-based logic withjava.time
-based logic to ensure compatibility and maintainability.Hibernate CriteriaBuilder: Hibernate’s CriteriaBuilder now cannot be reused for multiple CriteriaQuery instances using the createQuery method on the same builder instance. When we want to create multiple CriteriaQuery it should be associated with a new CriteriaBuilder instance, otherwise, it will throw an error saying java.lang.IllegalArgumentException: Already registered a copy: SqmBasicValuedSimplePath.
JPA Naming Strategy: The
SpringPhysicalNamingStrategy
class is no longer available in the latest Spring Boot jar. Therefore, any JPA configuration related to it should refer to the classorg.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
. For example:If component scanning does not work with the
scanBasePackages
attribute within the@SpringBootApplication
annotation, move that definition inside the@ComponentScan
annotation’sbasePackages
attribute. The same applies to any exclusions. When using the exclusion attribute in the@ComponentScan
annotation, apply the AspectJfilter-type
to exclude a list of packages for convenience.
III. JUnit Test Case related changes
JUnit Test Dependency: To run the JUnit tests, the following dependency is required and has now been added to
kernel-core
. For reference, see: kernel-core pom.xmlMVEL Dependency Update: If MVEL-related test cases are failing, update the MVEL dependency as per the kernel-applicanttype-api pom.xml file.
Spring Boot Test Error Fix:
If you encounter the following error:
Add the exclusion in the Spring Boot Test application as shown below:
This will exclude the DataSourceAutoConfiguration.class
, resolving the issue.
Mockito Related errors: The
Mockito
dependencies in thespring-boot-bom
were incorrect, but this has now been corrected inkernel-bom
. The Mockito version is now correctly set to3.4.3
.For reference, please refer to the kernel-bom pom.xml file.
Power-Mockito related issues:
Mockito Core Version Issue:
If you encounter the following error:
Solution: Ensure that the
mockito-core
version is set to3.4.3
.b. Maven Build Access Issues:
If the build fails due to access-related issues, add the following
argLine
configuration in themaven-surefire-plugin
section of thepom.xml
:
For example, if you are facing the following error:
then add the below command to the argument line.
If you encounter the
PBKDF2WithHmacSHA256 algorithm not supported
error, addjavax.crypto.*
to@PowerMockitoIgnore
as shown below:If you get the error
PowerMockitoInjectingAnnotationEngine.injectMocks() is not accessible
because it is a private method, usemockito-core version 3.11.2
in that module specifically.
Test Security Config: To configure test security, a
SecurityFilterChain
bean is required. The bean can be implemented as shown below:
For reference, see the implementation in the file TestSecurityConfig.java (lines 34-38)
If you encounter the following error: Error:
Use the -e
flag with the mvn clean install
command to enable stack trace printing:
After running the command, scroll to the top of the errors to locate the real root cause, as this error is not the original issue.
Dependency Version Issue: In
kernel-pdfgenerator-itext
, downgrade theitext-core
version from7.2.0
to7.1.0
to fix test case issues.Error: MockMvc - 401 or 403 Status If using
mockMvc
results in errors like the below:Check the
TestSecurityConfig
to ensure that the URLs are permitted. Configure it as shown below to allow all requests during JUnit tests:Component Scanning Fix: If component scanning does not work with the
scanBasePackages
attribute within the@SpringBootApplication
annotation, follow these steps:Move the
scanBasePackages
definition to the@ComponentScan
annotation’sbasePackages
attribute.The same approach applies to any exclusions.
When using the
exclusion
attribute in the@ComponentScan
annotation, use theASPECTJ
filter type to conveniently exclude specific packages. Example:
Test Properties Not Loaded: If JUnit tests are not loading the test properties, ensure that the following annotation is added to the test class:
Replace application.properties
with the correct properties file name, if different.
Note: Avoid Mixing JUnit 4 and JUnit 5
While this may not be directly related to Java migration, it is strongly recommended to use either JUnit 4 or JUnit 5 consistently throughout your test classes. Mixing the two versions can cause compatibility issues, leading to test failures or unexpected behavior.
Key Differences and Equivalents:
Assertions
JUnit 4:
org.junit.Assert
JUnit 5:
org.junit.jupiter.api.Assertions
Lifecycle Annotations
JUnit 4:
@Before
JUnit 5:
@BeforeEach
IV. Configuration Changes
1. Explicitly Configuring Ant Path Matcher
In Spring MVC, the default path-matching strategy has changed to PathPatternParser
. This can cause failures when processing Ant-style patterns. To avoid such issues, explicitly configure the application to use AntPathMatcher
by setting the appropriate property in your configuration file.
Example:
Refer to the implementation here: application-default.properties (lines 469-470)
2. Updated HTTP Header Size Property
The server.max-http-header-size
property is now deprecated. Use server.max-http-request-header-size
instead to configure the maximum HTTP request header size.
Example:
Refer to the implementation here: application-default.properties (lines 337-338)
V. Artifactory Changes
The following steps can be followed to configure Artifactory during the Java migration for the modules:
If a dynamic dependency is migrated for a module that needs to be loaded from the artifactory (either directly downloaded as a jar file or packaged in a zip file), it can be created as a new entry to the artifactory pom.xml file by mentioning the version and its path which is different from an existing entry.
The idea is to keep the existing artifacts unchanged to have the existing services unaffected and only add new entries that are differentiated by the new version in the jar file or the containing folder. Finally, when all modules migration is done we can remove the old version artifact entries.
While deploying a migrated service, it is essential to update the Docker run command or Helm chart configuration to include the appropriate argument for loading the newer version of the dynamic library from the Artifactory server.
VI. Docker file changes: [Yet to be Tested]
The following updates are done in the docker file.
FROM openjdk:11
FROM eclipse-temurin:21-jre-alpine
apt-get -y update
apk -q update
apt-get update -y
apk -q update
apt-get install -y
apk add -q
apk add -q unzip \
apk add -q unzip wget \
apt-get -y install
apk add -q
groupadd -g ${container_user_gid} ${container_user_group}
addgroup -g ${container_user_gid} ${container_user_group}
useradd -u ${container_user_uid} -g ${container_user_group} -s /bin/sh -m ${container_user}
adduser -s /bin/sh -u ${container_user_uid} -G ${container_user_group} -h /home/${container_user} --disabled-password ${container_user}
ARG container_user_uid=1001
ARG container_user_uid=1002
VII. Github Workflow changes:
If the new branch
develop-java21
is not included in the GitHub workflow push trigger, ensure that it is added.To see the changes made please refer to this commit.
Key Updates:
In the push trigger, the Java version was updated from:
java-version: 11
→java-version: 21
For PR checks, any references to the
kattu
repository in the GitHub workflow YAML files should now point to themaster-java21
branch. Please refer to the related commit here.
Note:
Please refer to the changes made for running the audit-service in the following pull requests (PRs):
While these changes apply generally to most modules, specific dependencies or code used by other modules may require additional, module-specific modifications.
5. Additional Module-wise Migration Guides
I. Resident Service:
Perform the following additional steps if you encounter the issue mentioned below during migration:
Interceptor Issue: An empty interceptor is not working with Hibernate 6. To resolve this, you need to implement the
Interceptor
interface.
As part of the Java upgrade from an older version, the above method is deprecated. Therefore, use the following method instead:
II. Registration Client:
The
registration-client
internally uses Derby as the local database. As part of the migration to Java 21, the Derby dialect has been updated fromDerbyTenSevenDialect
toDerbyDialect
to ensure compatibility.During the migration, issues were observed when converting request and response objects to
String
orMap
withregistration-processor
APIs. To address this:The
requestType
for some APIs was explicitly set toString
.The response is now received as an
Object
and converted to aMap
to handle exceptions.
Due to this change:
A non-migrated
registration-client
will not work with a migratedregistration-processor
.However, a migrated
registration-client
will work with a non-migratedregistration-processor
, or both modules must be migrated together.As part of the Java migration, JavaFX has been upgraded to version 21.0.3 to support the latest features. When setting up the Java 21-migrated
registration-client
repository in your IDE, you will need to:Download the required JavaFX ZIP file.
While running or debugging the
Initialization.java
class to start theregistration-client
application, we pass certain VM arguments to ensure the application runs correctly. One of these arguments specifies the path to the OpenJFX ZIP file. You will need to update the path in the VM arguments to point to the latest JavaFX ZIP file that was downloaded in the previous step. Additionally, a few changes have been made to the existing VM arguments to support Java 21. The updated VM arguments are listed below:
Running the Registration Client Downloader Docker image
The base image in the Dockerfile has been updated to
mosipdev/openjdk-21-jdk:latest
to support Java 21.The
registration-api-stub-impl
JAR dependency has been added to the Artifactory POM file. During the deployment of the registration-client, this dependency is pulled from Artifactory and bundled with other JAR files in thelib
folder. This approach avoids adding the dependency directly to theregistration-services
POM file. If custom implementations related to document scanning or geo-positioning are required, they can be pulled from Artifactory and bundled without modifying theregistration-client
codebase.Since JavaFX has been migrated to version 21.0.3, the JavaFX-related files, specifically
zulu21.34.19-ca-fx-jre21.0.3-win_x64.zip
, have been added to Artifactory. This ZIP file is used when preparing the registration-client downloadable ZIP file.Modifications have been made to the
configure.sh
script to support the above two changes.
III. Pre-Registration:
Please refer to the below points for additional steps for the deployment of the pre-registration module:
Pre-registration Batch Job Upgrade Scripts for PostgreSQL DB:
Run the scripts in the respective pre-registration batch job tables.
Pre-reg Service Migration:
Migrate the pre-reg service from Java 11 to Java 21. For more details, refer to the Pull Request #683 in the MOSIP repository.
Java 21 Jars:
Java 21 JARs can be taken from here.
Note:
Keycloak Role Removal: Please remove the INDIVIDUAL role from the mosip_prereg_client Available Roles in Keycloak.
IV. Web-Sub Migration:
Please refer to the following points while migrating the Web-Sub Java version:
The Web-Sub repository contains a Java-based module called
kafka-admin-client
.As part of the migration process, the
websub/kafka-admin-client
module has been updated to Java 17, the latest version supported by Ballerina.Additionally, Ballerina has been upgraded to the latest version, 2201.9.0 (Swan Lake Update 9), to ensure compatibility with Java 17. This upgrade enables the module to leverage the new features and improvements introduced in both Java 17 and the updated Ballerina version.
Last updated