diff --git a/plugin-modernizer-core/pom.xml b/plugin-modernizer-core/pom.xml
index 2fc34f459..b1495cf2d 100644
--- a/plugin-modernizer-core/pom.xml
+++ b/plugin-modernizer-core/pom.xml
@@ -117,6 +117,10 @@
org.openrewrite
rewrite-xml
+
+ org.openrewrite.recipe
+ rewrite-apache
+
org.openrewrite.recipe
rewrite-jenkins
diff --git a/plugin-modernizer-core/src/main/jte/pr-body-MigrateCommonsLangToJdkApi.jte b/plugin-modernizer-core/src/main/jte/pr-body-MigrateCommonsLangToJdkApi.jte
new file mode 100644
index 000000000..5992e62f2
--- /dev/null
+++ b/plugin-modernizer-core/src/main/jte/pr-body-MigrateCommonsLangToJdkApi.jte
@@ -0,0 +1,3 @@
+This PR migrates commons-lang usage to standard JDK APIs.
+
+It replaces commonly used Apache Commons Lang utility methods with equivalent methods from the Java standard library.
\ No newline at end of file
diff --git a/plugin-modernizer-core/src/main/jte/pr-title-MigrateCommonsLangToJdkApi.jte b/plugin-modernizer-core/src/main/jte/pr-title-MigrateCommonsLangToJdkApi.jte
new file mode 100644
index 000000000..c36c4c7a5
--- /dev/null
+++ b/plugin-modernizer-core/src/main/jte/pr-title-MigrateCommonsLangToJdkApi.jte
@@ -0,0 +1 @@
+Migrate commons-lang usage to JDK APIs
\ No newline at end of file
diff --git a/plugin-modernizer-core/src/main/resources/META-INF/rewrite/recipes.yml b/plugin-modernizer-core/src/main/resources/META-INF/rewrite/recipes.yml
index 16d129737..0c4539368 100644
--- a/plugin-modernizer-core/src/main/resources/META-INF/rewrite/recipes.yml
+++ b/plugin-modernizer-core/src/main/resources/META-INF/rewrite/recipes.yml
@@ -249,6 +249,16 @@ recipeList:
- io.jenkins.tools.pluginmodernizer.core.recipes.MigrateToJenkinsBaseLineProperty
---
type: specs.openrewrite.org/v1beta/recipe
+name: io.jenkins.tools.pluginmodernizer.MigrateCommonsLangToJdkApi
+displayName: Migrate commons-lang usage to JDK APIs
+description: Replace commons-lang utility methods with modern JDK API equivalents using OpenRewrite.
+tags: ['dependencies', 'migration']
+recipeList:
+ - org.openrewrite.apache.commons.lang.ApacheCommonsStringUtilsRecipes
+ - org.openrewrite.apache.commons.lang.IsNotEmptyToJdk
+ - org.openrewrite.java.RemoveUnusedImports
+---
+type: specs.openrewrite.org/v1beta/recipe
name: io.jenkins.tools.pluginmodernizer.AddCodeOwner
displayName: Add CODEOWNERS file
description: Adds a CODEOWNERS file to a Jenkins plugin.
diff --git a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/recipes/DeclarativeRecipesTest.java b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/recipes/DeclarativeRecipesTest.java
index a7b9c9a5c..1ea8bdfd6 100644
--- a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/recipes/DeclarativeRecipesTest.java
+++ b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/recipes/DeclarativeRecipesTest.java
@@ -3506,6 +3506,94 @@ public String getHtml() {
"""));
}
+ @Test
+ void migrateCommonsLangToJdkApi() {
+ rewriteRun(
+ spec -> {
+ var parser = JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true);
+
+ collectRewriteTestDependencies().forEach(parser::addClasspathEntry);
+
+ spec.recipeFromResource(
+ "/META-INF/rewrite/recipes.yml",
+ "io.jenkins.tools.pluginmodernizer.MigrateCommonsLangToJdkApi")
+ .parser(parser);
+ },
+ // Stub
+ java("""
+ package org.apache.commons.lang3;
+ public class StringUtils {
+ public static String defaultString(String str) {
+ return str == null ? "" : str;
+ }
+ public static String defaultString(String str, String defaultStr) {
+ return str == null ? defaultStr : str;
+ }
+ public static boolean isNotEmpty(String str) {
+ return str != null && !str.isEmpty();
+ }
+ }
+ """),
+ // Input → Output
+ java("""
+ import org.apache.commons.lang3.StringUtils;
+
+ class MyComponent {
+ public String getDefault(String input) {
+ return StringUtils.defaultString(input);
+ }
+ public String getDefaultWithFallback(String input) {
+ return StringUtils.defaultString(input, "N/A");
+ }
+ }
+ """, """
+ import java.util.Objects;
+
+ class MyComponent {
+ public String getDefault(String input) {
+ return Objects.toString(input, "");
+ }
+ public String getDefaultWithFallback(String input) {
+ return Objects.toString(input, "N/A");
+ }
+ }
+ """),
+ java("""
+ import org.apache.commons.lang3.StringUtils;
+
+ class Test {
+ boolean check(String str) {
+ return StringUtils.isNotEmpty(str);
+ }
+ }
+ """, """
+ class Test {
+ boolean check(String str) {
+ return str != null && !str.isEmpty();
+ }
+ }
+ """));
+ }
+
+ @Test
+ void noChangeWhenNoCommonsLang() {
+ rewriteRun(
+ spec -> {
+ var parser = JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true);
+ collectRewriteTestDependencies().forEach(parser::addClasspathEntry);
+
+ spec.recipeFromResource(
+ "/META-INF/rewrite/recipes.yml",
+ "io.jenkins.tools.pluginmodernizer.MigrateCommonsLangToJdkApi")
+ .parser(parser);
+ },
+ java("""
+ class Test {
+ String s = "hello";
+ }
+ """));
+ }
+
@Test
void migrateToJUnit5() {
rewriteRun(
diff --git a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/utils/TemplateUtilsTest.java b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/utils/TemplateUtilsTest.java
index e9ea3ce9f..26f9d699e 100644
--- a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/utils/TemplateUtilsTest.java
+++ b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/utils/TemplateUtilsTest.java
@@ -1,6 +1,7 @@
package io.jenkins.tools.pluginmodernizer.core.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -855,6 +856,46 @@ public void testFriendlyPrBodyMigrateCommonsLang2ToLang3AndCommonText() {
"Description");
}
+ @Test
+ public void testFriendlyPrTitleMigrateCommonsLangToJdkApi() {
+
+ // Mocks
+ Plugin plugin = mock(Plugin.class);
+ Recipe recipe = mock(Recipe.class);
+
+ doReturn("io.jenkins.tools.pluginmodernizer.MigrateCommonsLangToJdkApi")
+ .when(recipe)
+ .getName();
+
+ // Test
+ String result = TemplateUtils.renderPullRequestTitle(plugin, recipe);
+
+ // Assert
+ assertNotNull(result);
+ assertTrue(result.contains("commons-lang"));
+ assertEquals("Migrate commons-lang usage to JDK APIs", result);
+ }
+
+ @Test
+ public void testFriendlyPrBodyMigrateCommonsLangToJdkApi() {
+
+ // Mocks
+ Plugin plugin = mock(Plugin.class);
+ Recipe recipe = mock(Recipe.class);
+
+ doReturn("io.jenkins.tools.pluginmodernizer.MigrateCommonsLangToJdkApi")
+ .when(recipe)
+ .getName();
+
+ // Test
+ String result = TemplateUtils.renderPullRequestBody(plugin, recipe);
+
+ // Assert
+ assertNotNull(result);
+ assertTrue(result.contains("commons-lang"));
+ assertTrue(result.contains("standard JDK APIs"));
+ }
+
@Test
public void testFriendlyPrTitleMigrateJavaxAnnotationsToSpotbugs() {
diff --git a/scripts/validate_metadata.py b/scripts/validate_metadata.py
index 6b7e788a5..5db5f9039 100644
--- a/scripts/validate_metadata.py
+++ b/scripts/validate_metadata.py
@@ -94,6 +94,7 @@
"io.jenkins.tools.pluginmodernizer.EnsureIndexJelly",
"io.jenkins.tools.pluginmodernizer.MigrateTomakehurstToWiremock",
"io.jenkins.tools.pluginmodernizer.MigrateCommonsLang2ToLang3AndCommonText",
+ "io.jenkins.tools.pluginmodernizer.MigrateCommonsLangToJdkApi",
"io.jenkins.tools.pluginmodernizer.RemoveOldJavaVersionForModernJenkins",
"io.jenkins.tools.pluginmodernizer.SwitchToRenovate",
"io.jenkins.tools.pluginmodernizer.JavaxAnnotationsToSpotbugs",