Skip to main content

How to migrate code using OpenRewrite Receipes Java 21 Upgrade with Maven and Gradle Configuration

OpenRewrite is an automated refactoring ecosystem for source code, enabling developers to effectively eliminate technical debt within their repositories.

It consists of an auto-refactoring engine that runs prepackaged, open-source refactoring recipes for common framework migrations, security fixes, and stylistic consistency tasks – reducing your coding effort from hours or days to minutes. Build tool plugins like the OpenRewrite Gradle plugin and the OpenRewrite Maven plugin help you run these recipes on one repository at a time.

(Note: Any installation of external software or IDE plugin/extension is not required)
Reference Link: Introduction to OpenRewrite | OpenRewrite by Moderne

Types of Recipes
A recipe can represent a single, stand-alone operation or it can be linked together with other recipes to accomplish a larger goal such as a framework migration.

We can also write custom recipes: There are 2 ways to write custom recipes in OpenRewrite:
  • Imperative Recipes: An imperative recipe is built by extending the org.openrewrite.Recipe class and injecting configuration properties via its constructor.
  • Declarative Recipes: Declarative recipes are defined in YAML and loaded as part of OpenRewrite's managed Environment. A declarative recipe composes and configures other recipes via the OpenRewrite configuration file.
We can Link Recipes together as well to do multiple operations at once. Let's take a look at the l
ist of standard Recipes that required for Java 21 upgradation.

For Java: 
  • Org.openrewrite.java.migrate.UpgradeToJava21: To convert Java version to 21. To convert to any other version, just change the version number in the recipe.
For Spring Framework:
  • Org.openrewrite.java.spring.framework.UpgradeSpringFramework_6_1: Converting Spring Framework version to 6.1.
Spring Security
  • Org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_2Converting Spring security version to 6.2.
Spring Cloud
  • Org.openrewrite.java.spring.cloud2023.UpgradeSpringCloud_2023: Converting to the latest Spring Cloud 2023 release.
SpringBoot
  • Org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2: To convert Spring Boot version to 3.2.
Slf4j
  • Org.openrewrite.java.logging.slf4j.Log4jToSlf4j: To convert Log4j to Slf4j
Junit for Java
  • Org.openrewrite.java.testing.junit5.JUnit5BestPractices: To convert Junit Test cases
Junit for SpringBoot
  • Org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration: To convert Junit testcases from version 4 to 5
Mockito
  • Org.openrewrite.java.testing.mockito.Mockito1to5Migration: To convert Mockito framework from lower version to higher version(5.x)
PowerMockito
  • Org.openrewrite.java.testing.mockito.ReplacePowerMockito: Upgrade to Mockito 5 and replace PowerMockito with raw Mockito
Jackson Fasterxml
  • io.quarkus.updates.core.quarkus30.JacksonJavaxToJakarta: To replace existing Jackson dependencies with their counterparts that are compatible with Jakarta EE.
Java xml soap
  • Org.openrewrite.java.migrate.jakarta.JavaxXmlSoapToJakartaXmlSoap: To migrate from javax to Jakarta namespace for this dependency
ApacheHttpClient
  • Org.openrewrite.apache.httpclient5.UpgradeApacheHttpClient_5: To migrate from apache Http Component to version 4 to 5
Custom Recipes: We can create custom recipes, to upgrade below dependencies as per the analysis done so far, we may require creating additional custom recipes. How to create custom recipes, example for that is given at the end of this document.

For examples for the third-party Java components which are not covered by OpenRewrite
Infinispan
  • Liquibase
  • Apache CXF
  • Apache Flink
  • Avro
  • KafkaClient
  • OWASP ESAPI
  • TestNG
Migration Report:
In OpenRewrite Plugin, it generates a migration report ‘rewrite.patch’ file (in the build/reports/rewrite folder)prior to the migration. So, we can check what will be changed from that file.

Steps to migrate Java using OpenRewrite Plugin:
1. Clone legacy Java 8 code base into your IntelliJ
2. Click on the Maven icon in the right side of IntelliJ and then click on the below shown icon to execute Maven goal.


OR
Click on the Gradle icon in the right side of IntelliJ and then click on the below shown icon to execute Gradle task.


3. Then execute mvn clean install, to ensure project with no errors.
OR
Then execute Gradle clean build, to ensure project with no errors.
4. Then add relevant dependencies to build file (maven/Gradle) to migrate from java lower to higher version as given in the below section.
5. Then execute Maven goal - mvn rewrite:dryRun OR Gradle task - Gradle rewriteDryRun.
It will not make any changes to the code, instead it will generate a rewrite.patch file which will give the details of changes it will make when you execute added recipes. 
Mostly this file will be stored in your project build folder under the rewrite folder. Please take a backup of this file once dryRun is completed, because the build folder will be changed each time you build.

6. After verifying the changes in rewrite patch, we can execute Maven goal mvn rewrite:Run OR Gradle task Gradle rewriteRun, this will migrate the code and build files, etc.

7. As shown below you can also see the files got changed from the console, once execute step 6.
Gradle dependencies:
Step 1: Add Plugin
plugins {
id ‘org.openrewrite.rewrite’ version ‘6.20.0’
}
Step 2: Add rewrite section to declare active recipes
rewrite {
activeRecipe("org.openrewrite.java.migrate.UpgradeToJava21")
activeRecipe("org.openrewrite.java.spring.framework.UpgradeSpringFramework_6_1")
activeRecipe("org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_2")
activeRecipe("org.openrewrite.java.spring.cloud2023.UpgradeSpringCloud_2023")
activeRecipe("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2")
activeRecipe("org.openrewrite.java.logging.slf4j.Log4jToSlf4j")
activeRecipe("org.openrewrite.java.testing.junit5.JUnit5BestPractices")
activeRecipe("org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration")
activeRecipe("org.openrewrite.java.testing.mockito.Mockito1to5Migration")
activeRecipe("org.openrewrite.java.testing.mockito.ReplacePowerMockito")
activeRecipe("io.quarkus.updates.core.quarkus30.JacksonJavaxToJakarta")
activeRecipe("org.openrewrite.java.migrate.jakarta.JavaxXmlSoapToJakartaXmlSoap")
exportDatatables = true
}
Step 3: Add mavenCentral if not exist
repositories {
mavenCentral()
}
Step 4: Add dependencies for declared Recipes
dependencies {
rewrite group: 'org.openrewrite.recipe’, name: ‘rewrite-migrate-java', version: ‘2.22.0’
rewrite group: 'org.openrewrite.recipe’, name: ‘rewrite-spring’, version: ‘5.17.0’
rewrite group: 'org.openrewrite.recipe’, name: ‘rewrite-logging-frameworks', version: ‘2.12.0’
rewrite group: 'org.openrewrite.recipe’, name: ‘rewrite-testing-frameworks', version: ‘2.15.0’
rewrite group: ‘org.openrewrite.recipe’, name: ‘rewrite-third-party’, version: ‘0.6.0’
}

Maven dependencies:
<project>
<build>
<plugins>
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>5.39.0</version>
<configuration>
<exportDatatables>true</exportDatatables>
<activeRecipes>
<recipe>org.openrewrite.java.migrate.UpgradeToJava21</recipe>
<recipe>org.openrewrite.java.spring.framework.UpgradeSpringFramework_6_1</recipe>
<recipe>org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_2</recipe>
<recipe>org.openrewrite.java.spring.cloud2023.UpgradeSpringCloud_2023</recipe>
<recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2</recipe>
<recipe>org.openrewrite.java.logging.slf4j.Log4jToSlf4j</recipe>
<recipe>org.openrewrite.java.testing.junit5.JUnit5BestPractices</recipe>
<recipe>org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration</recipe>
<recipe>org.openrewrite.java.testing.mockito.Mockito1to5Migration</recipe>
<recipe>org.openrewrite.java.testing.mockito.ReplacePowerMockito</recipe>
<recipe>org.openrewrite.java.migrate.jakarta.JacksonJavaxToJakarta</recipe>
<recipe>org.openrewrite.java.migrate.jakarta.JavaxXmlSoapToJakartaXmlSoap</recipe>
</activeRecipes>
</configuration>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-migrate-java</artifactId>
<version>2.22.0</version>
</dependency>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-spring</artifactId>
<version>5.17.0</version>
</dependency>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-logging-frameworks</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-testing-frameworks</artifactId>
<version>2.15.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

Manual Migration : There are few areas where openRewrite is suggesting for manual migration, so we captured one of that scenario from our PoC migration report, as in the below screenshot.


Please Note:
  • The basic free version of OpenRwerite appears adequate for Java 21 upgrade.
  • Most migrations can be handled with default recipes.
  • Custom recipes are required for some Java components-related implementation
  • During the POC, we observed 70% of the code was properly converted, while the remaining 30% requires manual fixes.
  • The 'rewrite.patch' provides detailed code conversion details. OpenRwerite suggests manual migration for inherited classes and Spring.Security packages.
  • Increase in Sonar and Snyk violations observed post-migration.
Optional: Steps to Add Custom Imperative Recipes Using OpenRewrite
Create a New Java Class for the Recipe: Define your custom recipe by creating a new Java class that extends Recipe. Create a file named src/main/java/com/example/recipes/CustomRecipe.java with the following content:

package com.example.recipes;
import org.openrewrite.Recipe;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J;
public class CustomRecipe extends Recipe {
@Override
public String getDisplayName() {
return "Custom Recipe";
}
@Override
public String getDescription() {
return "A custom recipe for demonstration purposes.";
}
@Override
protected JavaIsoVisitor<ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
// Add your custom transformation logic here
return super.visitMethodDeclaration(method, ctx);
}
};
}
}

2. Implement the Recipe Logic: Override the getVisitor() method to provide the logic for your recipe. In the visitMethodDeclaration method, add the logic for the transformations you want to apply. For example, you can change method names, modify annotations, etc.

3. Register the Recipe: Ensure your custom recipe is registered and can be discovered by OpenRewrite. You can do this by adding a META-INF/rewrite/rewrite.yml file:

type: specs.openrewrite.org/v1beta/recipe
name: com.example.recipes.CustomRecipe
displayName: Custom Recipe
description: A custom recipe for demonstration purposes.
recipeList:
- com.example.recipes.CustomRecipe

Steps to Add Custom Declarative Recipes Using OpenRewrite
1. Create a YAML Recipe File: Define your custom recipe in a YAML file. This file will contain the specifications for the transformations you want to apply.
Create a file named src/main/resources/custom-recipe.yml with the following content:
type: specs.openrewrite.org/v1beta/recipe
name: com.example.CustomRecipe
displayName: Custom Recipe
description: A custom recipe for demonstration purposes.
recipeList:
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: com.oldpackage.OldClass
newFullyQualifiedTypeName: com.newpackage.NewClass
- org.openrewrite.java.ChangeMethodName:
methodPattern: com.oldpackage.OldClass#oldMethod
newMethodName: newMethod

2. Add the Recipe to Your Project: Place the YAML file in your project directory, typically under src/main/resources.
3. Configure the rewrite-maven-plugin: Update your pom.xml to include the rewrite-maven-plugin and reference your custom recipe.
<configuration>
<activeRecipes>
<recipe>com.example.CustomRecipe</recipe>
</activeRecipes>
<configLocation>src/main/resources/custom-recipe.yml</configLocation>
</configuration>

Happy Learning!

Comments

Popular posts from this blog

Fix: "Cannot set a credential for principal 'sa' . (Microsoft SQL Server, Error: 15535)" and “User is not associated with a trusted sql server connection" Errors

Recently, I had happen to struck with the following errors when I tried to reset the SA password through the SQL Server 2008 R2 Express. " Cannot set a credential for principal 'sa' . (Microsoft SQL Server, Error: 15535) " and then, “ User is not associated with a trusted sql server connection " From my research I have found the solution and that perfectly worked in SQL management studio. Hence, I thought of sharing my findings with others. ========================================================= Advertisement: Choosing .NET Core Worker Services OR Windows Services? ========================================================= Steps to reset the password in SQL Server 2008 R2 Express and fix for the errors: Step 1. Go to SQL Server Instance -> Properties/Security tab and change the mode to SQL Server authentication mode. Step 2. Go to Security/Logins, then open 'sa' login properties,          a. Uncheck the "Enforce passwor...

How to Set Up SonarQube in IntelliJ: A Step-by-Step Guide

SonarQube in IntelliJ: (Looking for SonarLint then check here:  How to install and execute Sonarlint ) Setting up SonarQube in IntelliJ can significantly enhance your code quality by identifying bugs and vulnerabilities. Follow these steps to integrate SonarQube with IntelliJ: Step 1 :  Install SonarLint PluginOpen IntelliJ and navigate to Settings. Go to Plugins > Marketplace. Search for SonarLint, install it, and restart the IDE. Step 2 : Configure SonarLint Click on SonarLint at the bottom left of IntelliJ. Select Configure SonarLint. In the popup, check the box for Bind project to SonarQube/SonarCloud and click on Configure the connection. Step 3 :  Set Up the ConnectionIn the new popup, click the + icon to add a new connection. If the + icon is not visible, go to File > Settings > New UI and disable it. Apply changes and restart the IDE. Name your connection and select SonarQube. Enter the SonarQube URL (e.g., https://sonar.prod.company.com) and click Next....

How to implement JUnit 5 in Your Maven or Gradle Project: A Step-by-Step Guide for Java Testing

Unit Testing JUnit Learn how to integrate JUnit 5 in your Maven or Gradle project for efficient Java testing. This guide covers adding dependencies, creating test classes, writing test methods, and running tests in IntelliJ IDEA and Visual Studio Code to ensure robust and error-free code. Step 1: Create a Simple Java Application Create a simple Java application, for example, “Copilot Demo,” and select Maven or Gradle as the build system through IntelliJ IDEA or Visual Studio Code. Step 2: Add JUnit Dependency For Maven: Add the following dependency to your pom.xml file inside the <dependencies> tag: XML <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.11.2</version> <scope>test</scope> </dependency> For Gradle: Add the following dependencies to your build.gradle file: dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.11.2' } Step ...