Repository: openrewrite/rewrite
Pull request #5240: Optionally change String literals from `ChangePackage` and `ChangeType`
Author: login: Laurens-W
Created: 2025-04-01T12:54:57Z | Merged: 2025-04-09T11:35:15Z

<!--
Thank you for taking the time to contribute to OpenRewrite!
Feel free to delete any sections that don't apply to your pull request.
-->

## What's changed?
<!-- A brief description of the changes in this pull request -->
Added a Reference implementation for finding type/package references in String literals

## What's your motivation?
<!-- This can link to close a separate issue, or be described on the pull request itself -->
- https://github.com/openrewrite/rewrite-migrate-java/pull/693#discussion_r2014430310

## Anything in particular you'd like reviewers to focus on?
<!-- You can also start a discussion on particular aspects of your implementation on the files tab yourself. -->

## Anyone you would like to review specifically?
<!-- @mention them here -->

## Have you considered any alternatives or workarounds?
<!-- Any other ways to solve the problem, or ways to work around the problem. -->

## Any additional context
<!-- Any thoughts you would like to share in addition to the above. -->

### Checklist
- [x] I've added unit tests to cover both positive and negative cases
- [x] I've read and applied the [recipe conventions and best practices](https://docs.openrewrite.org/authoring-recipes/recipe-conventions-and-best-practices)
- [x] I've used the IntelliJ IDEA auto-formatter on affected files



Diff:
diff --git a/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/ChangeTypeBenchmark.java b/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/ChangeTypeBenchmark.java
index b67d1d0b790..a60eba34435 100644
--- a/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/ChangeTypeBenchmark.java
+++ b/rewrite-benchmarks/src/jmh/java/org/openrewrite/benchmarks/java/ChangeTypeBenchmark.java
@@ -44,7 +44,7 @@ public static void main(String[] args) throws RunnerException {
 
     @Benchmark
     public void changeType(JavaCompilationUnitState state) {
-        new ChangeType("java.util.List", "java.util.Collection", null)
+        new ChangeType("java.util.List", "java.util.Collection", null, null)
                 .run(state.getSourceSet(), new InMemoryExecutionContext());
     }
 }
diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/ChangePackageAdaptabilityTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/ChangePackageAdaptabilityTest.java
index c403edfa5fe..4f017a48018 100644
--- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/ChangePackageAdaptabilityTest.java
+++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/ChangePackageAdaptabilityTest.java
@@ -33,7 +33,7 @@ class ChangePackageAdaptabilityTest implements RewriteTest {
 
     @Override
     public void defaults(RecipeSpec spec) {
-        spec.recipe(new ChangePackage("a.b", "x.y", false));
+        spec.recipe(new ChangePackage("a.b", "x.y", false, null));
     }
 
     @DocumentExample
@@ -103,7 +103,7 @@ class A {
     @Test
     void renamePackageRecursive() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("org.foo", "org.foo.test", true)),
+          spec -> spec.recipe(new ChangePackage("org.foo", "org.foo.test", true, null)),
           groovy(
             """
               package org.foo.internal
@@ -127,7 +127,7 @@ class Test {
     @Test
     void changeDefinition() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("org.foo", "x.y.z", false)),
+          spec -> spec.recipe(new ChangePackage("org.foo", "x.y.z", false, null)),
           groovy(
             """
               package org.foo
diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/ChangeTypeAdaptabilityTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/ChangeTypeAdaptabilityTest.java
index bc6a594143a..4cbfeec8ba6 100644
--- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/ChangeTypeAdaptabilityTest.java
+++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/ChangeTypeAdaptabilityTest.java
@@ -35,7 +35,7 @@ class ChangeTypeAdaptabilityTest implements RewriteTest {
 
     @Override
     public void defaults(RecipeSpec spec) {
-        spec.recipe(new ChangeType("a.b.Original", "x.y.Target", true));
+        spec.recipe(new ChangeType("a.b.Original", "x.y.Target", true, null));
     }
 
     @SuppressWarnings("GrPackage")
@@ -100,7 +100,7 @@ class A {
     @Test
     void changeDefinition() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("file", "newFile", false)),
+          spec -> spec.recipe(new ChangeType("file", "newFile", false, null)),
           groovy(
             """
               class file {
@@ -124,7 +124,7 @@ class newFile {
     @Test
     void changeTypeAttributionImplicitUsage() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.List", "java.util.ArrayList", false)),
+          spec -> spec.recipe(new ChangeType("java.util.List", "java.util.ArrayList", false, null)),
           groovy(
             """
               import java.util.Collections
diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java
index 458c0cf8e18..cbd05324b46 100644
--- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java
+++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java
@@ -39,7 +39,7 @@ class ChangePackageTest implements RewriteTest {
 
     @Override
     public void defaults(RecipeSpec spec) {
-        spec.recipe(new ChangePackage("org.openrewrite", "org.openrewrite.test", null));
+        spec.recipe(new ChangePackage("org.openrewrite", "org.openrewrite.test", null, null));
     }
 
     @Language("java")
@@ -66,7 +66,8 @@ void renamePackageNonRecursive() {
           spec -> spec.recipe(new ChangePackage(
             "org.openrewrite",
             "org.openrewrite.test",
-            false
+            false,
+            null
           )),
           java(
             """
@@ -85,7 +86,8 @@ void renameUsingSimplePackageName() {
           spec -> spec.recipe(new ChangePackage(
             "org.openrewrite",
             "openrewrite",
-            false
+            false,
+            null
           )),
           java(
             """
@@ -353,7 +355,8 @@ void renamePackageRecursiveImported() {
           spec -> spec.recipe(new ChangePackage(
             "org.openrewrite",
             "org.openrewrite.test",
-            true
+            true,
+            null
           )),
           java(
             """
@@ -393,6 +396,7 @@ void renamePackageNullRecursiveImportedCheckStrictPackageMatch() {
           spec -> spec.recipe(new ChangePackage(
             "org.openrewrite.other",
             "org.openrewrite.test.other",
+            null,
             null
           )),
           java(
@@ -451,7 +455,8 @@ void renamePackageImportedCheckStrictPackageMatch() {
           spec -> spec.recipe(new ChangePackage(
             "org.openrewrite.other",
             "org.openrewrite.test.other",
-            false
+            false,
+            null
           )),
           java(
             """
@@ -509,7 +514,8 @@ void renamePackageRecursiveImportedStrictPackageMatch() {
           spec -> spec.recipe(new ChangePackage(
             "org.openrewrite.other",
             "org.openrewrite.test.other",
-            true
+            true,
+            null
           )),
           java(
             """
@@ -999,6 +1005,7 @@ void moveToSubPackageRemoveImport() {
           spec -> spec.recipe(new ChangePackage(
             "com.acme.project",
             "com.acme.product",
+            null,
             null
           )),
           java(
@@ -1040,7 +1047,8 @@ void moveToSubPackageDoNotRemoveImport() {
           spec -> spec.recipe(new ChangePackage(
             "com.acme.project",
             "com.acme.product",
-            true
+            true,
+            null
           )),
           java(
             """
@@ -1114,7 +1122,7 @@ void callWorker() {
     @Test
     void typeInNestedPackageInheritingFromTypeInBasePackage() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("java.util", "util", null)),
+          spec -> spec.recipe(new ChangePackage("java.util", "util", null, null)),
           java(
             """
               import java.util.concurrent.ConcurrentHashMap;
@@ -1470,7 +1478,7 @@ void changeTypeWithInnerClass() {
           spec -> spec.recipe(new ChangePackage(
             "com.acme.product",
             "com.acme.product.v2",
-            null
+            null, null
           )),
           java(
             """
@@ -1522,7 +1530,7 @@ public String work2() {
     @Test
     void updateImportPrefixWithEmptyPackage() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("a.b", "", false)),
+          spec -> spec.recipe(new ChangePackage("a.b", "", false, null)),
           java(
             """
               package a.b;
@@ -1546,7 +1554,8 @@ void updateClassPrefixWithEmptyPackage() {
             ChangePackage(
             "a.b",
             "",
-            false
+            false,
+            null
           )),
           java(
             """
@@ -1570,7 +1579,8 @@ void renameSingleSegmentPackage() {
             ChangePackage(
             "x",
             "y",
-            false
+            false,
+            null
           )),
           java(
             """
@@ -1595,7 +1605,8 @@ void removePackage() {
             ChangePackage(
             "x.y.z",
             "x",
-            false
+            false,
+            null
           )),
           java(
             """
@@ -1709,7 +1720,7 @@ public enum MyEnum {
     @Test
     void changePackageInSpringXml() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("test.type", "test.test.type", true)),
+          spec -> spec.recipe(new ChangePackage("test.type", "test.test.type", true, null)),
           xml(
             """
               <?xml version="1.0" encoding="UTF-8"?>
@@ -1730,7 +1741,7 @@ void changePackageInSpringXml() {
     @Test
     void changeTypeInPropertiesFile() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("java.lang", "java.cool", true)),
+          spec -> spec.recipe(new ChangePackage("java.lang", "java.cool", true, null)),
           properties(
             """
               a.property=java.lang.String
@@ -1749,7 +1760,7 @@ void changeTypeInPropertiesFile() {
     @Test
     void changePackageInYaml() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("java.lang", "java.cool", true)),
+          spec -> spec.recipe(new ChangePackage("java.lang", "java.cool", true, null)),
           yaml(
             """
               root:
@@ -1768,10 +1779,50 @@ void changePackageInYaml() {
         );
     }
 
+    @Test
+    void changePackageInLiteral() {
+        rewriteRun(
+          spec -> spec.recipe(new ChangePackage("javax.type", "jakarta.type", true, true)),
+          java(
+            """
+              class Test {
+                  String ref = "javax.type.A";
+                  String extendedRef = "there is a type reference here -> javax.type.A <- hopefully it only replaces that";
+              }
+              """,
+            """
+              class Test {
+                  String ref = "jakarta.type.A";
+                  String extendedRef = "there is a type reference here -> jakarta.type.A <- hopefully it only replaces that";
+              }
+              """
+          )
+        );
+    }
+
+    @Test
+    void changePackageInLiteralTrueButNoLiteral() {
+        rewriteRun(
+          spec -> spec.recipe(new ChangePackage("test.type", "test.test.type", true, true)),
+          java(
+            """
+              package test.type;
+              class Test {
+              }
+              """,
+            """
+              package test.test.type;
+              class Test {
+              }
+              """
+          )
+        );
+    }
+
     @Test
     void changeNonRecursivePackageInYamlKey() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("org.apache.http", "org.apache.hc.core5.http", false)),
+          spec -> spec.recipe(new ChangePackage("org.apache.http", "org.apache.hc.core5.http", false, null)),
           yaml(
             """
               logging:
diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java
index 6a0712cd028..10c79261ff0 100644
--- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java
+++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java
@@ -38,7 +38,7 @@ class ChangeTypeTest implements RewriteTest {
 
     @Override
     public void defaults(RecipeSpec spec) {
-        spec.recipe(new ChangeType("a.A1", "a.A2", true));
+        spec.recipe(new ChangeType("a.A1", "a.A2", true, null));
     }
 
     @Language("java")
@@ -63,7 +63,7 @@ public void foo() {}
     @Test
     void doNotAddJavaLangWrapperImports() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.lang.Integer", "java.lang.Long", true)),
+          spec -> spec.recipe(new ChangeType("java.lang.Integer", "java.lang.Long", true, null)),
           java(
             "public class ThinkPositive { private Integer fred = 1;}",
             "public class ThinkPositive { private Long fred = 1;}"
@@ -74,7 +74,7 @@ void doNotAddJavaLangWrapperImports() {
     @Test
     void okWithTopLevelType() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.Map$Entry", "java.util.List", true)),
+          spec -> spec.recipe(new ChangeType("java.util.Map$Entry", "java.util.List", true, null)),
           java(
             """
               import java.util.Map;
@@ -105,7 +105,7 @@ public List respond() {
     void conflictingImports() {
         rewriteRun(
           spec -> spec.recipe(new ChangeType(
-            "java.util.List", "kotlin.collections.Set", true)),
+            "java.util.List", "kotlin.collections.Set", true, null)),
           java(
             """
               import java.util.*;
@@ -129,7 +129,7 @@ class Test {
     @Test
     void starImport() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.logging.LoggingMXBean", "java.lang.management.PlatformLoggingMXBean", true)),
+          spec -> spec.recipe(new ChangeType("java.util.logging.LoggingMXBean", "java.lang.management.PlatformLoggingMXBean", true, null)),
           java(
             """
               import java.util.logging.*;
@@ -158,7 +158,7 @@ static void method() {
     @Test
     void allowJavaLangSubpackages() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.logging.LoggingMXBean", "java.lang.management.PlatformLoggingMXBean", true)),
+          spec -> spec.recipe(new ChangeType("java.util.logging.LoggingMXBean", "java.lang.management.PlatformLoggingMXBean", true, null)),
           java(
             """
               import java.util.logging.LoggingMXBean;
@@ -187,7 +187,7 @@ static void method() {
     @Test
     void unnecessaryImport() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("test.Outer.Inner", "java.util.ArrayList", true)),
+          spec -> spec.recipe(new ChangeType("test.Outer.Inner", "java.util.ArrayList", true, null)),
           java(
             """
               import test.Outer;
@@ -220,7 +220,7 @@ public static class Inner {
     @Test
     void changeInnerClassToOuterClass() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.Map$Entry", "java.util.List", true)),
+          spec -> spec.recipe(new ChangeType("java.util.Map$Entry", "java.util.List", true, null)),
           java(
             """
               import java.util.Map;
@@ -249,7 +249,7 @@ class Test {
     @Test
     void changeStaticFieldAccess() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.io.File", "my.pkg.List", true)),
+          spec -> spec.recipe(new ChangeType("java.io.File", "my.pkg.List", true, null)),
           java(
             """
               import java.io.File;
@@ -281,7 +281,7 @@ void dontAddImportWhenNoChangesWereMade() {
     @Test
     void replaceWithNestedType() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.io.File", "java.util.Map$Entry", true)),
+          spec -> spec.recipe(new ChangeType("java.io.File", "java.util.Map$Entry", true, null)),
           java(
             """
               import java.io.File;
@@ -305,7 +305,7 @@ class Test {
     @Test
     void replacePrivateNestedType() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.A.B1", "a.A.B2", false)),
+          spec -> spec.recipe(new ChangeType("a.A.B1", "a.A.B2", false, null)),
           java(
             """
               package a;
@@ -329,7 +329,7 @@ private static class B2 {}
     @Test
     void deeplyNestedInnerClass() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.A.B.C", "a.A.B.C2", false)),
+          spec -> spec.recipe(new ChangeType("a.A.B.C", "a.A.B.C2", false, null)),
           java(
             """
               package a;
@@ -394,7 +394,7 @@ public class B extends A2 {}
     @Test
     void annotation() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.c.A1", "a.b.d.A2", true)),
+          spec -> spec.recipe(new ChangeType("a.b.c.A1", "a.b.d.A2", true, null)),
           java("package a.b.c;\npublic @interface A1 {}"),
           java("package a.b.d;\npublic @interface A2 {}"),
           java(
@@ -413,7 +413,7 @@ void annotation() {
     @Test
     void array2() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.acme.product.Pojo", "com.acme.product.v2.Pojo", true)),
+          spec -> spec.recipe(new ChangeType("com.acme.product.Pojo", "com.acme.product.v2.Pojo", true, null)),
           java(
             """
               package com.acme.product;
@@ -510,8 +510,8 @@ public class A {
     void classDecl() {
         rewriteRun(
           spec -> spec.recipes(
-            new ChangeType("a.A1", "a.A2", true),
-            new ChangeType("I1", "I2", true)
+            new ChangeType("a.A1", "a.A2", true, null),
+            new ChangeType("I1", "I2", true, null)
           ),
           java(a1),
           java(a2),
@@ -837,7 +837,7 @@ public void test() {
 
     @Test
     void staticImports2() {
-        rewriteRun(spec -> spec.recipe(new ChangeType("com.acme.product.RunnableFactory", "com.acme.product.v2.RunnableFactory", true)),
+        rewriteRun(spec -> spec.recipe(new ChangeType("com.acme.product.RunnableFactory", "com.acme.product.v2.RunnableFactory", true, null)),
           java(
             """
               package com.acme.product;
@@ -879,7 +879,7 @@ public void work() {
     @Test
     void staticConstant() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.acme.product.RunnableFactory", "com.acme.product.v2.RunnableFactory", true)),
+          spec -> spec.recipe(new ChangeType("com.acme.product.RunnableFactory", "com.acme.product.v2.RunnableFactory", true, null)),
           java(
             """
               package com.acme.product;
@@ -920,7 +920,7 @@ public void work() {
     @Test
     void primitiveToClass() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("int", "java.lang.Integer", true)),
+          spec -> spec.recipe(new ChangeType("int", "java.lang.Integer", true, null)),
           java(
             """
               class A {
@@ -945,7 +945,7 @@ Integer getFoo() {
     @Test
     void classToPrimitive() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.lang.Integer", "int", true)),
+          spec -> spec.recipe(new ChangeType("java.lang.Integer", "int", true, null)),
           java(
             """
               class A {
@@ -971,7 +971,7 @@ int getFoo() {
     @Test
     void importOrdering() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.yourorg.a.A", "com.myorg.b.B", true)),
+          spec -> spec.recipe(new ChangeType("com.yourorg.a.A", "com.myorg.b.B", true, null)),
           java(
             """
               package com.yourorg.a;
@@ -1015,7 +1015,7 @@ public class Foo {
     @Test
     void changeTypeWithInnerClass() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.acme.product.OuterClass", "com.acme.product.v2.OuterClass", true)),
+          spec -> spec.recipe(new ChangeType("com.acme.product.OuterClass", "com.acme.product.v2.OuterClass", true, null)),
           java(
             """
               package com.acme.product;
@@ -1068,7 +1068,7 @@ public String work2() {
     @Test
     void uppercaseInPackage() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.acme.product.util.accessDecision.AccessVote", "com.acme.product.v2.util.accessDecision.AccessVote", true)),
+          spec -> spec.recipe(new ChangeType("com.acme.product.util.accessDecision.AccessVote", "com.acme.product.v2.util.accessDecision.AccessVote", true, null)),
           java(
             """
               package com.acme.product.util.accessDecision;
@@ -1109,7 +1109,7 @@ public AccessVote vote() {
     @Test
     void lambda() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.acme.product.Procedure", "com.acme.product.Procedure2", true)),
+          spec -> spec.recipe(new ChangeType("com.acme.product.Procedure", "com.acme.product.Procedure2", true, null)),
           java(
             """
               package com.acme.product;
@@ -1149,7 +1149,7 @@ void callWorker() {
     @Test
     void assignment() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.acme.product.util.accessDecision.AccessVote", "com.acme.product.v2.util.accessDecision.AccessVote", true)),
+          spec -> spec.recipe(new ChangeType("com.acme.product.util.accessDecision.AccessVote", "com.acme.product.v2.util.accessDecision.AccessVote", true, null)),
           java(
             """
               package com.acme.product.util.accessDecision;
@@ -1195,7 +1195,7 @@ public AccessVote vote(Object input) {
     @Test
     void ternary() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.acme.product.util.accessDecision.AccessVote", "com.acme.product.v2.util.accessDecision.AccessVote", true)),
+          spec -> spec.recipe(new ChangeType("com.acme.product.util.accessDecision.AccessVote", "com.acme.product.v2.util.accessDecision.AccessVote", true, null)),
           java(
             """
               package com.acme.product.util.accessDecision;
@@ -1237,7 +1237,7 @@ public AccessVote vote(Object input) {
     @Test
     void changeTypeInTypeDeclaration() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("de.Class2", "de.Class1", false)),
+          spec -> spec.recipe(new ChangeType("de.Class2", "de.Class1", false, null)),
           java(
             """
               package de;
@@ -1255,7 +1255,7 @@ public class Class1 {}
     @Test
     void doNotChangeTypeInTypeDeclaration() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("de.Class2", "de.Class1", true)),
+          spec -> spec.recipe(new ChangeType("de.Class2", "de.Class1", true, null)),
           java(
             """
               package de;
@@ -1268,7 +1268,7 @@ public class Class2 {}
     @Test
     void javadocs() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.List", "java.util.Collection", true)),
+          spec -> spec.recipe(new ChangeType("java.util.List", "java.util.Collection", true, null)),
           java(
             """
               import java.util.List;
@@ -1298,7 +1298,7 @@ class Test {
     @Test
     void onlyUpdateApplicableImport() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("com.acme.product.factory.V1Factory", "com.acme.product.factory.V1FactoryA", true)),
+          spec -> spec.recipe(new ChangeType("com.acme.product.factory.V1Factory", "com.acme.product.factory.V1FactoryA", true, null)),
           java(
             """
               package com.acme.product.factory;
@@ -1364,7 +1364,7 @@ static String getMyItemStaticImport() {
     @Test
     void filePathMatchWithNoMatchedClassFqn() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Target", false)),
+          spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Target", false, null)),
           java(
             """
               package a;
@@ -1381,7 +1381,7 @@ public class NoMatch {
     @Test
     void onlyChangeTypeWithoutMatchedFilePath() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Target", false)),
+          spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Target", false, null)),
           java(
             """
               package a.b;
@@ -1405,7 +1405,7 @@ public class Target {
     @Test
     void renameClassAndFilePath() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Target", false)),
+          spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Target", false, null)),
           java(
             """
               package a.b;
@@ -1429,7 +1429,7 @@ public class Target {
     @Test
     void updateImportPrefixWithEmptyPackage() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.Original", "Target", false)),
+          spec -> spec.recipe(new ChangeType("a.b.Original", "Target", false, null)),
           java(
             """
               package a.b;
@@ -1453,7 +1453,7 @@ class Target {
     @Test
     void updateClassPrefixWithEmptyPackage() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.Original", "Target", false)),
+          spec -> spec.recipe(new ChangeType("a.b.Original", "Target", false, null)),
           java(
             """
               package a.b;
@@ -1473,7 +1473,7 @@ class Target {
     @Test
     void renameInnerClass() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.C$Original", "a.b.C$Target", false)),
+          spec -> spec.recipe(new ChangeType("a.b.C$Original", "a.b.C$Target", false, null)),
           java(
             """
               package a.b;
@@ -1497,7 +1497,7 @@ public static class Target {
     @Test
     void changePathOfNonPublicClass() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.C", "x.y.Z", false)),
+          spec -> spec.recipe(new ChangeType("a.b.C", "x.y.Z", false, null)),
           java(
             """
               package a.b;
@@ -1522,7 +1522,7 @@ class D {
     @Test
     void renamePackage() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Original", false)),
+          spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Original", false, null)),
           java(
             """
               package a.b;
@@ -1542,7 +1542,7 @@ class Original {
     @Test
     void renameClass() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.b.Original", "a.b.Target", false)),
+          spec -> spec.recipe(new ChangeType("a.b.Original", "a.b.Target", false, null)),
           java(
             """
               package a.b;
@@ -1561,7 +1561,7 @@ class Target {
     @Test
     void updateMethodType() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.A1", "a.A2", false)),
+          spec -> spec.recipe(new ChangeType("a.A1", "a.A2", false, null)),
           java(
             """
               package a;
@@ -1696,7 +1696,7 @@ <T extends A2> T method(T t) {
     @Test
     void changeConstructor() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("a.A1", "a.A2", false)),
+          spec -> spec.recipe(new ChangeType("a.A1", "a.A2", false, null)),
           java(
             """
               package a;
@@ -1727,7 +1727,7 @@ public A2() {
     @Test
     void updateJavaTypeClassKindAnnotation() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("org.openrewrite.Test1", "org.openrewrite.Test2", false)),
+          spec -> spec.recipe(new ChangeType("org.openrewrite.Test1", "org.openrewrite.Test2", false, null)),
           java(
             """
               package org.openrewrite;
@@ -1787,7 +1787,7 @@ void method() {}
     @Test
     void changeJavaTypeClassKindEnum() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("org.openrewrite.MyEnum1", "org.openrewrite.MyEnum2", false)),
+          spec -> spec.recipe(new ChangeType("org.openrewrite.MyEnum1", "org.openrewrite.MyEnum2", false, null)),
           java(
             """
               package org.openrewrite;
@@ -1839,7 +1839,7 @@ public void test(String s) {
     @Test
     void doesNotModifyInnerClassesIfIgnoreDefinitionTrue() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("Test.InnerA", "Test.InnerB", true)),
+          spec -> spec.recipe(new ChangeType("Test.InnerA", "Test.InnerB", true, null)),
           java(
             """
               public class Test {
@@ -1876,7 +1876,7 @@ public void test(String s) {
     @Test
     void doesNotModifyPackageOfSibling() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("org.openrewrite.Test", "org.openrewrite.subpackage.Test", false)),
+          spec -> spec.recipe(new ChangeType("org.openrewrite.Test", "org.openrewrite.subpackage.Test", false, null)),
           java(
             """
               package org.openrewrite;
@@ -1923,10 +1923,12 @@ void changeTypeOfStaticImportOfNestedEnumValueUsed() {
           recipeSpec -> recipeSpec.recipes(
             new ChangeType(
               "org.codehaus.jackson.map.ObjectMapper",
-              "com.fasterxml.jackson.databind.ObjectMapper", true),
+              "com.fasterxml.jackson.databind.ObjectMapper",
+              true,
+              null),
             new ChangeType(
               "org.codehaus.jackson.map.SerializationConfig$Feature",
-              "com.fasterxml.jackson.databind.SerializationFeature", true)
+              "com.fasterxml.jackson.databind.SerializationFeature", true, null)
           ),
           java(
             """
@@ -1987,6 +1989,7 @@ void shouldFullyQualifyWhenNewTypeIsAmbiguous() {
           spec -> spec.recipe(new ChangeType(
             "javax.annotation.Nonnull",
             "org.checkerframework.checker.nullness.qual.NonNull",
+            null,
             null)),
           // language=java
           java(
@@ -2021,7 +2024,8 @@ void shouldNotFullyQualifyWhenNewTypeIsAlreadyUsed() {
           spec -> spec.recipe(new ChangeType(
             "org.a.A",
             "org.ab.AB",
-            true)),
+            true,
+            null)),
           java(
             """
               package org.a;
@@ -2065,7 +2069,7 @@ class Letters {
     @Test
     void changeTypeInSpringXml() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("test.type.A", "test.type.B", true)),
+          spec -> spec.recipe(new ChangeType("test.type.A", "test.type.B", true, null)),
           xml(
             """
               <?xml version="1.0" encoding="UTF-8"?>
@@ -2081,7 +2085,50 @@ void changeTypeInSpringXml() {
               """
           )
         );
+    }
+
+    @Test
+    void changeTypeInLiteral() {
+        rewriteRun(
+          spec -> spec.recipe(new ChangeType("javax.type.A", "jakarta.type.B", true, true)),
+          java(
+            """
+              class Test {
+                  String ref = "javax.type.A";
+                  String refNested = "javax.type.other.A";
+                  String extendedRef = "there is a type reference here -> javax.type.A <- hopefully it only replaces that";
+              }
+              """,
+            """
+              class Test {
+                  String ref = "jakarta.type.B";
+                  String refNested = "javax.type.other.A";
+                  String extendedRef = "there is a type reference here -> jakarta.type.B <- hopefully it only replaces that";
+              }
+              """
+          )
+        );
+    }
 
+    @Test
+    void changeTypeInLiteralTrueButNoLiteralReference() {
+        rewriteRun(
+          spec -> spec
+            .recipe(new ChangeType("a.A1", "a.A2", true, true))
+            .parser(JavaParser.fromJavaVersion().dependsOn(a1, a2)),
+          java(
+            """
+              import a.A1;
+              
+              public class B extends A1 {}
+              """,
+            """
+              import a.A2;
+              
+              public class B extends A2 {}
+              """
+          )
+        );
     }
 
     @Test
@@ -2094,7 +2141,7 @@ void changeTypeWorksOnDirectInvocations() {
               public class HelloClass {}
               """,
             spec -> spec.beforeRecipe((source) -> {
-                TreeVisitor<?, ExecutionContext> visitor = new ChangeType("hello.HelloClass", "hello.GoodbyeClass", false).getVisitor();
+                TreeVisitor<?, ExecutionContext> visitor = new ChangeType("hello.HelloClass", "hello.GoodbyeClass", false, null).getVisitor();
 
                 J.CompilationUnit cu = (J.CompilationUnit) visitor.visit(source, new InMemoryExecutionContext());
                 assertEquals("GoodbyeClass", cu.getClasses().get(0).getSimpleName());
@@ -2108,7 +2155,7 @@ public class HelloClass {}
     @Test
     void changeTypeInPropertiesFile() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.lang.String", "java.lang.Integer", true)),
+          spec -> spec.recipe(new ChangeType("java.lang.String", "java.lang.Integer", true, null)),
           properties(
             """
               a.property=java.lang.String
@@ -2126,7 +2173,7 @@ void changeTypeInPropertiesFile() {
     @Test
     void changeTypeInYaml() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.lang.String", "java.lang.Integer", true)),
+          spec -> spec.recipe(new ChangeType("java.lang.String", "java.lang.Integer", true, null)),
           yaml(
             """
               root:
@@ -2151,7 +2198,7 @@ void changeTypeInYaml() {
     @Issue("https://github.com/openrewrite/rewrite/issues/4773")
     void noRenameOfTypeWithMatchingPrefix() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("org.codehaus.jackson.annotate.JsonIgnoreProperties", "com.fasterxml.jackson.annotation.JsonIgnoreProperties", false))
+          spec -> spec.recipe(new ChangeType("org.codehaus.jackson.annotate.JsonIgnoreProperties", "com.fasterxml.jackson.annotation.JsonIgnoreProperties", false, null))
             .parser(JavaParser.fromJavaVersion()
               .dependsOn(
                 """
@@ -2200,7 +2247,7 @@ public boolean isDirty() {
     @Issue("https://github.com/openrewrite/rewrite/issues/4764")
     void changeTypeOfInnerClass() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("foo.A$Builder", "bar.A$Builder", true))
+          spec -> spec.recipe(new ChangeType("foo.A$Builder", "bar.A$Builder", true, null))
             .parser(JavaParser.fromJavaVersion().dependsOn(
                 """
                   package foo;
diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveImportTest.java
index 48b113af4dd..a09d785c002 100755
--- a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveImportTest.java
+++ b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveImportTest.java
@@ -618,7 +618,7 @@ void generateNewUUIDPerUnfoldedImport() {
         rewriteRun(
           spec -> spec.recipes(
             removeImport("java.util.Collection"),
-            new ChangeType("java.util.List", "java.util.Collection", null)
+            new ChangeType("java.util.List", "java.util.Collection", null, null)
           ),
           java(
             """
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
index dd32bea75a5..c2ce61cc52c 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
@@ -21,6 +21,8 @@
 import org.jspecify.annotations.Nullable;
 import org.openrewrite.*;
 import org.openrewrite.internal.ListUtils;
+import org.openrewrite.java.trait.Literal;
+import org.openrewrite.java.trait.Traits;
 import org.openrewrite.java.tree.*;
 import org.openrewrite.marker.SearchResult;
 import org.openrewrite.trait.Reference;
@@ -29,6 +31,8 @@
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
 
 import static java.util.Objects.requireNonNull;
 
@@ -61,6 +65,12 @@ public class ChangePackage extends Recipe {
     @Nullable
     Boolean recursive;
 
+    @Option(displayName = "Apply to string literal",
+            description = "When set to `true`, String literal will also be taken into account.",
+            required = false)
+    @Nullable
+    Boolean visitStringLiterals;
+
     @Override
     public String getInstanceNameSuffix() {
         return String.format("`%s` to `%s`", oldPackageName, newPackageName);
@@ -91,6 +101,15 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                 stopAfterPreVisit();
                 if (tree instanceof JavaSourceFile) {
                     JavaSourceFile cu = (JavaSourceFile) tree;
+                    if (Boolean.TRUE.equals(visitStringLiterals) && Traits.literal().asVisitor((Literal lit, AtomicBoolean bool) -> {
+                        String string = lit.getString();
+                        if (string != null && string.contains(oldPackageName)) {
+                            bool.set(true);
+                        }
+                        return lit.getTree();
+                    }).reduce(cu, new AtomicBoolean(false), getCursor().getParentTreeCursor()).get()) {
+                        return SearchResult.found(cu);
+                    }
                     if (cu.getPackageDeclaration() != null) {
                         String original = cu.getPackageDeclaration().getExpression()
                                 .printTrimmed(getCursor()).replaceAll("\\s", "");
@@ -169,9 +188,9 @@ public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) {
             if (((J.FieldAccess) f).isFullyQualifiedClassReference(oldPackageName)) {
                 Cursor parent = getCursor().getParent();
                 if (parent != null &&
-                    // Ensure the parent isn't a J.FieldAccess OR the parent doesn't match the target package name.
-                    (!(parent.getValue() instanceof J.FieldAccess) ||
-                     (!(((J.FieldAccess) parent.getValue()).isFullyQualifiedClassReference(newPackageName))))) {
+                        // Ensure the parent isn't a J.FieldAccess OR the parent doesn't match the target package name.
+                        (!(parent.getValue() instanceof J.FieldAccess) ||
+                                (!(((J.FieldAccess) parent.getValue()).isFullyQualifiedClassReference(newPackageName))))) {
 
                     f = TypeTree.build(((JavaType.FullyQualified) newPackageType).getFullyQualifiedName())
                             .withPrefix(f.getPrefix());
@@ -235,6 +254,19 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct
             return updateType(javaType);
         }
 
+        @Override
+        public J visitLiteral(J.Literal literal, ExecutionContext ctx) {
+            J.Literal lit = literal;
+            if (Boolean.TRUE.equals(visitStringLiterals) && literal.getType() == JavaType.Primitive.String) {
+                Pattern pat = Pattern.compile("\\b" + oldPackageName + "\\b");
+                if (lit.getValue() != null && pat.matcher((String) lit.getValue()).find()) {
+                    lit = lit.withValue(((String) lit.getValue()).replace(oldPackageName, newPackageName))
+                            .withValueSource(lit.getValueSource().replace(oldPackageName, newPackageName));
+                }
+            }
+            return super.visitLiteral(lit, ctx);
+        }
+
         @Override
         public J postVisit(J tree, ExecutionContext ctx) {
             J j = super.postVisit(tree, ctx);
@@ -373,14 +405,14 @@ private String getNewPackageName(String packageName) {
 
         private boolean isTargetFullyQualifiedType(JavaType.@Nullable FullyQualified fq) {
             return fq != null &&
-                   (fq.getPackageName().equals(oldPackageName) && !fq.getClassName().isEmpty() ||
-                    isTargetRecursivePackageName(fq.getPackageName()));
+                    (fq.getPackageName().equals(oldPackageName) && !fq.getClassName().isEmpty() ||
+                            isTargetRecursivePackageName(fq.getPackageName()));
         }
 
         private boolean isTargetRecursivePackageName(String packageName) {
             return (recursive == null || recursive) &&
-                   packageName.startsWith(oldPackageName + ".") &&
-                   !packageName.startsWith(newPackageName);
+                    packageName.startsWith(oldPackageName + ".") &&
+                    !packageName.startsWith(newPackageName);
         }
 
     }
diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
index f4b02a7fd9a..3ec78f5cbc6 100644
--- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
+++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
@@ -21,6 +21,8 @@
 import org.openrewrite.*;
 import org.openrewrite.internal.ListUtils;
 import org.openrewrite.java.search.UsesType;
+import org.openrewrite.java.trait.Literal;
+import org.openrewrite.java.trait.Traits;
 import org.openrewrite.java.tree.*;
 import org.openrewrite.marker.Markers;
 import org.openrewrite.marker.SearchResult;
@@ -30,6 +32,7 @@
 import java.nio.file.Paths;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
 
 import static java.util.Objects.requireNonNull;
 
@@ -49,11 +52,17 @@ public class ChangeType extends Recipe {
 
     @Option(displayName = "Ignore type definition",
             description = "When set to `true` the definition of the old type will be left untouched. " +
-                          "This is useful when you're replacing usage of a class but don't want to rename it.",
+                    "This is useful when you're replacing usage of a class but don't want to rename it.",
             required = false)
     @Nullable
     Boolean ignoreDefinition;
 
+    @Option(displayName = "Apply to string literal",
+            description = "When set to `true`, String literal will also be taken into account.",
+            required = false)
+    @Nullable
+    Boolean visitStringLiterals;
+
     @Override
     public String getDisplayName() {
         return "Change type";
@@ -97,6 +106,15 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                     if (!Boolean.TRUE.equals(ignoreDefinition) && containsClassDefinition(cu, oldFullyQualifiedTypeName)) {
                         return SearchResult.found(cu);
                     }
+                    if (Boolean.TRUE.equals(visitStringLiterals) && Traits.literal().asVisitor((Literal lit, AtomicBoolean bool) -> {
+                        String string = lit.getString();
+                        if (string != null && string.contains(oldFullyQualifiedTypeName)) {
+                            bool.set(true);
+                        }
+                        return lit.getTree();
+                    }).reduce(cu, new AtomicBoolean(false), getCursor().getParentTreeCursor()).get()) {
+                        return SearchResult.found(cu);
+                    }
                     return new UsesType<>(oldFullyQualifiedTypeName, true).visitNonNull(cu, ctx);
                 } else if (tree instanceof SourceFileWithReferences) {
                     SourceFileWithReferences cu = (SourceFileWithReferences) tree;
@@ -116,7 +134,7 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
             public @Nullable Tree preVisit(@Nullable Tree tree, ExecutionContext ctx) {
                 stopAfterPreVisit();
                 if (tree instanceof J) {
-                    return new JavaChangeTypeVisitor(oldFullyQualifiedTypeName, newFullyQualifiedTypeName, ignoreDefinition).visit(tree, ctx, requireNonNull(getCursor().getParent()));
+                    return new JavaChangeTypeVisitor(oldFullyQualifiedTypeName, newFullyQualifiedTypeName, ignoreDefinition, visitStringLiterals).visit(tree, ctx, requireNonNull(getCursor().getParent()));
                 } else if (tree instanceof SourceFileWithReferences) {
                     SourceFileWithReferences sourceFile = (SourceFileWithReferences) tree;
                     SourceFileWithReferences.References references = sourceFile.getReferences();
@@ -135,20 +153,27 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
     private static class JavaChangeTypeVisitor extends JavaVisitor<ExecutionContext> {
         private final JavaType.Class originalType;
         private final JavaType targetType;
-
+        private final String oldFullyQualifiedTypeName;
+        private final String newFullyQualifiedTypeName;
 
         private J.@Nullable Identifier importAlias;
 
         @Nullable
         private final Boolean ignoreDefinition;
 
+        @Nullable
+        private final Boolean visitLiterals;
+
         private final Map<JavaType, JavaType> oldNameToChangedType = new IdentityHashMap<>();
         private final Set<String> topLevelClassnames = new HashSet<>();
 
-        private JavaChangeTypeVisitor(String oldFullyQualifiedTypeName, String newFullyQualifiedTypeName, @Nullable Boolean ignoreDefinition) {
+        private JavaChangeTypeVisitor(String oldFullyQualifiedTypeName, String newFullyQualifiedTypeName, @Nullable Boolean ignoreDefinition, @Nullable Boolean visitLiterals) {
+            this.oldFullyQualifiedTypeName = oldFullyQualifiedTypeName;
+            this.newFullyQualifiedTypeName = newFullyQualifiedTypeName;
             this.originalType = JavaType.ShallowClass.build(oldFullyQualifiedTypeName);
             this.targetType = JavaType.buildType(newFullyQualifiedTypeName);
             this.ignoreDefinition = ignoreDefinition;
+            this.visitLiterals = visitLiterals;
             importAlias = null;
         }
 
@@ -199,6 +224,19 @@ public J visitImport(J.Import import_, ExecutionContext ctx) {
             return updateType(javaType);
         }
 
+        @Override
+        public J visitLiteral(J.Literal literal, ExecutionContext ctx) {
+            J.Literal lit = literal;
+            if (Boolean.TRUE.equals(visitLiterals) && literal.getType() == JavaType.Primitive.String) {
+                Pattern pat = Pattern.compile("\\b" + oldFullyQualifiedTypeName + "\\b");
+                if (lit.getValue() != null && pat.matcher((String) lit.getValue()).find()) {
+                    lit = lit.withValue(((String) lit.getValue()).replace(oldFullyQualifiedTypeName, newFullyQualifiedTypeName))
+                            .withValueSource(lit.getValueSource().replace(oldFullyQualifiedTypeName, newFullyQualifiedTypeName));
+                }
+            }
+            return super.visitLiteral(lit, ctx);
+        }
+
         private void addImport(JavaType.FullyQualified owningClass) {
             if (importAlias != null) {
                 maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, importAlias.getSimpleName(), true);
@@ -337,7 +375,7 @@ public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) {
         public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) {
             // Do not modify the identifier if it's on a inner class definition.
             if (Boolean.TRUE.equals(ignoreDefinition) && getCursor().getParent() != null &&
-                getCursor().getParent().getValue() instanceof J.ClassDeclaration) {
+                    getCursor().getParent().getValue() instanceof J.ClassDeclaration) {
                 return super.visitIdentifier(ident, ctx);
             }
             // if the ident's type is equal to the type we're looking for, and the classname of the type we're looking for is equal to the ident's string representation
@@ -374,7 +412,7 @@ public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) {
                         if (anImport.isStatic() && anImport.getQualid().getTarget().getType() != null) {
                             JavaType.FullyQualified fqn = TypeUtils.asFullyQualified(anImport.getQualid().getTarget().getType());
                             if (fqn != null && TypeUtils.isOfClassType(fqn, originalType.getFullyQualifiedName()) &&
-                                ident.getSimpleName().equals(anImport.getQualid().getSimpleName())) {
+                                    ident.getSimpleName().equals(anImport.getQualid().getSimpleName())) {
                                 JavaType.FullyQualified targetFqn = (JavaType.FullyQualified) targetType;
                                 maybeAddImport((targetFqn).getFullyQualifiedName(), ident.getSimpleName());
                                 break;
@@ -397,7 +435,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx)
                         if (anImport.isStatic() && anImport.getQualid().getTarget().getType() != null) {
                             JavaType.FullyQualified fqn = TypeUtils.asFullyQualified(anImport.getQualid().getTarget().getType());
                             if (fqn != null && TypeUtils.isOfClassType(fqn, originalType.getFullyQualifiedName()) &&
-                                method.getSimpleName().equals(anImport.getQualid().getSimpleName())) {
+                                    method.getSimpleName().equals(anImport.getQualid().getSimpleName())) {
                                 JavaType.FullyQualified targetFqn = (JavaType.FullyQualified) targetType;
 
                                 addImport(targetFqn);
@@ -567,9 +605,9 @@ private boolean hasNoConflictingImport(@Nullable JavaSourceFile sf) {
             for (J.Import anImport : sf.getImports()) {
                 JavaType.FullyQualified currType = TypeUtils.asFullyQualified(anImport.getQualid().getType());
                 if (currType != null &&
-                    !TypeUtils.isOfType(currType, oldType) &&
-                    !TypeUtils.isOfType(currType, newType) &&
-                    currType.getClassName().equals(newType.getClassName())) {
+                        !TypeUtils.isOfType(currType, oldType) &&
+                        !TypeUtils.isOfType(currType, newType) &&
+                        currType.getClassName().equals(newType.getClassName())) {
                     return false;
                 }
             }
@@ -640,8 +678,8 @@ private String fqnToPath(String fullyQualifiedName) {
         private boolean updatePath(JavaSourceFile sf, String oldPath, String newPath) {
             return !oldPath.equals(newPath) && sf.getClasses().stream()
                     .anyMatch(o -> !o.hasModifier(J.Modifier.Type.Private) &&
-                                   o.getType() != null && !o.getType().getFullyQualifiedName().contains("$") &&
-                                   TypeUtils.isOfClassType(o.getType(), getTopLevelClassName(originalType).getFullyQualifiedName()));
+                            o.getType() != null && !o.getType().getFullyQualifiedName().contains("$") &&
+                            TypeUtils.isOfClassType(o.getType(), getTopLevelClassName(originalType).getFullyQualifiedName()));
         }
 
         @Override
diff --git a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/ChangePackageTest.java b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/ChangePackageTest.java
index c507ff5914e..194ee7639a0 100644
--- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/ChangePackageTest.java
+++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/ChangePackageTest.java
@@ -30,7 +30,7 @@
 class ChangePackageTest implements RewriteTest {
     @Override
     public void defaults(RecipeSpec spec) {
-        spec.recipe(new ChangePackage("a.b", "x.y", false));
+        spec.recipe(new ChangePackage("a.b", "x.y", false, null));
     }
 
     @DocumentExample
@@ -97,7 +97,7 @@ class A {
     @Test
     void renamePackageRecursive() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("org.foo", "org.foo.test", true)),
+          spec -> spec.recipe(new ChangePackage("org.foo", "org.foo.test", true, null)),
           kotlin(
             """
               package org.foo.internal
@@ -118,7 +118,7 @@ class Test
     @Test
     void changeDefinition() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("org.foo", "x.y.z", false)),
+          spec -> spec.recipe(new ChangePackage("org.foo", "x.y.z", false, null)),
           kotlin(
             """
               package org.foo
@@ -139,7 +139,7 @@ class Test
     @Test
     void changePackageNameWithInheritance() {
         rewriteRun(
-          spec -> spec.recipe(new ChangePackage("org.a", "org.b", false)),
+          spec -> spec.recipe(new ChangePackage("org.a", "org.b", false, null)),
           kotlin(
             """
               package org.a
diff --git a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/ChangeTypeTest.java b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/ChangeTypeTest.java
index 65a4ad8723a..96264c2a409 100644
--- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/ChangeTypeTest.java
+++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/ChangeTypeTest.java
@@ -30,7 +30,7 @@
 class ChangeTypeTest implements RewriteTest {
     @Override
     public void defaults(RecipeSpec spec) {
-        spec.recipe(new ChangeType("a.b.Original", "x.y.Target", true));
+        spec.recipe(new ChangeType("a.b.Original", "x.y.Target", true, null));
     }
 
     @Test
@@ -268,7 +268,7 @@ class A {
     @Test
     void changeDefinition() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("file", "newFile", false)),
+          spec -> spec.recipe(new ChangeType("file", "newFile", false, null)),
           kotlin(
             """
               class file {
@@ -287,7 +287,7 @@ class newFile {
     @Test
     void addImportAlias() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true)),
+          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true, null)),
           kotlin(
             """
               import java.util.ArrayList as MyList
@@ -314,7 +314,7 @@ fun main() {
     @Test
     void updateImportAlias() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true)),
+          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true, null)),
           kotlin(
             """
               import java.util.ArrayList as MyList
@@ -341,7 +341,7 @@ fun main() {
     @Test
     void usingAliasOnly() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true)),
+          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true, null)),
           kotlin(
             """
               import java.util.ArrayList as MyList
@@ -364,7 +364,7 @@ fun main() {
     @Test
     void implicitImport() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true)),
+          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true, null)),
           kotlin(
             """
               fun main() {
@@ -385,7 +385,7 @@ fun main() {
     @Test
     void qualifiedReference() {
         rewriteRun(
-          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true)),
+          spec -> spec.recipe(new ChangeType("java.util.ArrayList", "java.util.LinkedList", true, null)),
           kotlin(
             """
               import java.util.ArrayList as MyList
diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java
index 8005d5cf090..9cdc9db96b9 100644
--- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java
+++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java
@@ -1305,7 +1305,7 @@ void dependencyThatIsTransitivelyProvidedWithWrongScopeShouldBeAdded() {
               new AddDependency("org.checkerframework", "checker-qual", "3.44.0",
                 null, null, null, "main.java.checkerframework..*", null, null, null, null,
                 true),
-              new ChangePackage("main.java.checkerframework", "org.checkerframework", true)
+              new ChangePackage("main.java.checkerframework", "org.checkerframework", true, null)
             ),
           mavenProject("parent",
             pomXml(
@@ -1407,7 +1407,7 @@ void dependencyThatIsTransitivelyProvidedWithCorrectScopeShouldNotBeAdded() {
               new AddDependency("org.checkerframework", "checker-qual", "3.44.0",
                 null, null, null, "main.java.checkerframework..*", null, null, null, null,
                 true),
-              new ChangePackage("main.java.checkerframework", "org.checkerframework", true)
+              new ChangePackage("main.java.checkerframework", "org.checkerframework", true, null)
             ),
           mavenProject("parent",
             pomXml(
diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java b/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java
index f2ad9404f93..2b4ee57ac01 100644
--- a/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java
+++ b/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java
@@ -33,11 +33,6 @@ public class PropertiesReference implements Reference {
     Cursor cursor;
     Kind kind;
 
-    @Override
-    public Kind getKind() {
-        return kind;
-    }
-
     @Override
     public String getValue() {
         if (getTree() instanceof Properties.Entry) {
diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringXmlReference.java b/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringXmlReference.java
index 0d0db0a51da..88e20898bea 100644
--- a/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringXmlReference.java
+++ b/rewrite-xml/src/main/java/org/openrewrite/xml/trait/SpringXmlReference.java
@@ -35,11 +35,6 @@ public class SpringXmlReference extends XmlReference {
     Cursor cursor;
     Kind kind;
 
-    @Override
-    public Kind getKind() {
-        return kind;
-    }
-
     public static class Provider extends AbstractProvider<SpringXmlReference> {
         private static final SimpleTraitMatcher<SpringXmlReference> matcher = new SimpleTraitMatcher<SpringXmlReference>() {
             private final Pattern referencePattern = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(?:\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*");
diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlApplicationConfigReference.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlApplicationConfigReference.java
index 693f6bcdc77..0e0aca2718d 100644
--- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlApplicationConfigReference.java
+++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlApplicationConfigReference.java
@@ -31,7 +31,6 @@
 @EqualsAndHashCode(callSuper = false)
 public class YamlApplicationConfigReference extends YamlReference {
     Cursor cursor;
-    @Getter
     Kind kind;
 
     public static class Provider extends YamlProvider {


Reviews:
user: login: github-actions[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T13:01:33Z
user: login: github-actions[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:22:09Z
user: login: github-actions[bot]
body: Some suggestions could not be made:
* rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java
  * lines 191-200
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T14:16:51Z
user: login: timtebeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T12:46:01Z
user: login: Laurens-W
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T15:13:40Z
user: login: Laurens-W
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T15:13:58Z
user: login: github-actions[bot]
body: Some suggestions could not be made:
* rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java
  * lines 191-199
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T15:32:22Z
user: login: github-actions[bot]
body: Some suggestions could not be made:
* rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java
  * lines 191-200
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T15:40:08Z
user: login: timtebeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T16:19:15Z
user: login: Laurens-W
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T15:45:05Z
user: login: timtebeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T08:46:36Z
user: login: Laurens-W
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T10:02:05Z
user: login: Laurens-W
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T10:02:59Z
user: login: Laurens-W
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T10:13:14Z
user: login: github-actions[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T11:52:49Z
user: login: github-actions[bot]
body: Some suggestions could not be made:
* rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
  * lines 165-164
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T12:47:04Z
user: login: timtebeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T10:53:27Z
user: login: timtebeek
body: Good to see! Could you also ensure downstream usage of these recipes is updated?
- https://github.com/openrewrite/rewrite-static-analysis/blob/4fa996ad05ba4def81c9fbf89b0ce7114d971f64/src/main/java/org/openrewrite/staticanalysis/LowercasePackage.java#L88
- https://github.com/search?q=org%3Aopenrewrite+%22new+changetype%22+language%3AJava&type=code

We of course have a recipe to help you there; both for OpenRewrite and Moderne recipe modules 🙏🏻  
- https://app.moderne.io/recipes/org.openrewrite.java.AddNullMethodArgument
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-09T11:08:59Z
user: login: jevanlingen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:38:39Z

Review comments:
diff_hunk: @@ -0,0 +1,88 @@
+package org.openrewrite.java.trait;
path: rewrite-java/src/main/java/org/openrewrite/java/trait/StringLiteralReference.java
user: login: github-actions[bot]
body: ```suggestion
/*
 * Copyright 2025 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openrewrite.java.trait;
```
created_at: 2025-04-01T13:01:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package org.openrewrite.java.trait;
+
+import lombok.Value;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.Cursor;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.SourceFile;
+import org.openrewrite.Tree;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+import org.openrewrite.trait.Reference;
+import org.openrewrite.trait.SimpleTraitMatcher;
+
+import java.util.regex.Pattern;
+
+@Value
+public class StringLiteralReference implements Reference {
+    Cursor cursor;
+    Kind kind;
+    Pattern referencePattern = Pattern.compile("\\p{javaJavaIdentifierStart}*\\.\\p{javaJavaIdentifierStart}*");
+
+    @Override
+    public String getValue() {
+        if (getTree() instanceof J.Literal) {
+            if (((J.Literal) getTree()).getType().equals(JavaType.Primitive.String)) {
path: rewrite-java/src/main/java/org/openrewrite/java/trait/StringLiteralReference.java
user: login: github-actions[bot]
body: ```suggestion
            if (((J.Literal) getTree()).getType() == JavaType.Primitive.String) {
```
created_at: 2025-04-01T13:01:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package org.openrewrite.java.trait;
+
+import lombok.Value;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.Cursor;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.SourceFile;
+import org.openrewrite.Tree;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+import org.openrewrite.trait.Reference;
+import org.openrewrite.trait.SimpleTraitMatcher;
+
+import java.util.regex.Pattern;
+
+@Value
+public class StringLiteralReference implements Reference {
+    Cursor cursor;
+    Kind kind;
+    Pattern referencePattern = Pattern.compile("\\p{javaJavaIdentifierStart}*\\.\\p{javaJavaIdentifierStart}*");
+
+    @Override
+    public String getValue() {
+        if (getTree() instanceof J.Literal) {
+            if (((J.Literal) getTree()).getType().equals(JavaType.Primitive.String)) {
+                String literal = (String) ((J.Literal) getTree()).getValue();
+                java.util.regex.Matcher match = referencePattern.matcher(literal);
+                if (match.find()) {
+                    return match.group(0);
+                }
+            }
+        }
+        throw new IllegalArgumentException("getTree() must be a J.Literal: " + getTree().getClass());
+    }
+
+    @Override
+    public boolean supportsRename() {
+        return true;
+    }
+
+    @Override
+    public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) {
+        Tree tree = cursor.getValue();
+        if (tree instanceof J.Literal) {
+            J.Literal literal = (J.Literal) tree;
+            if (((J.Literal) tree).getType().equals(JavaType.Primitive.String)) {
path: rewrite-java/src/main/java/org/openrewrite/java/trait/StringLiteralReference.java
user: login: github-actions[bot]
body: ```suggestion
            if (((J.Literal) tree).getType() == JavaType.Primitive.String) {
```
created_at: 2025-04-01T13:01:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package org.openrewrite.java.trait;
+
+import lombok.Value;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.Cursor;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.SourceFile;
+import org.openrewrite.Tree;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+import org.openrewrite.trait.Reference;
+import org.openrewrite.trait.SimpleTraitMatcher;
+
+import java.util.regex.Pattern;
+
+@Value
+public class StringLiteralReference implements Reference {
+    Cursor cursor;
+    Kind kind;
+    Pattern referencePattern = Pattern.compile("\\p{javaJavaIdentifierStart}*\\.\\p{javaJavaIdentifierStart}*");
+
+    @Override
+    public String getValue() {
+        if (getTree() instanceof J.Literal) {
+            if (((J.Literal) getTree()).getType().equals(JavaType.Primitive.String)) {
+                String literal = (String) ((J.Literal) getTree()).getValue();
+                java.util.regex.Matcher match = referencePattern.matcher(literal);
+                if (match.find()) {
+                    return match.group(0);
+                }
+            }
+        }
+        throw new IllegalArgumentException("getTree() must be a J.Literal: " + getTree().getClass());
+    }
+
+    @Override
+    public boolean supportsRename() {
+        return true;
+    }
+
+    @Override
+    public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) {
+        Tree tree = cursor.getValue();
+        if (tree instanceof J.Literal) {
+            J.Literal literal = (J.Literal) tree;
+            if (((J.Literal) tree).getType().equals(JavaType.Primitive.String)) {
+                return literal.withValue(((String)literal.getValue()).replace(getValue(), renamer.rename(this))).withValueSource(literal.getValueSource().replace(getValue(), renamer.rename(this)));
+            }
+        }
+        return tree;
+    }
+
+    public static class Provider extends AbstractProvider<StringLiteralReference> {
+        private static final SimpleTraitMatcher<StringLiteralReference> matcher = new SimpleTraitMatcher<StringLiteralReference>() {
+            private final Pattern referencePattern = Pattern.compile("\\p{javaJavaIdentifierStart}*\\.\\p{javaJavaIdentifierStart}*");
+
+            @Override
+            protected @Nullable StringLiteralReference test(Cursor cursor) {
+                Object value = cursor.getValue();
+                if (value instanceof J.Literal) {
+                    if (((J.Literal) value).getType().equals(JavaType.Primitive.String)) {
path: rewrite-java/src/main/java/org/openrewrite/java/trait/StringLiteralReference.java
user: login: github-actions[bot]
body: ```suggestion
                    if (((J.Literal) value).getType() == JavaType.Primitive.String) {
```
created_at: 2025-04-01T13:01:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+package org.openrewrite.java.trait;
path: rewrite-java/src/test/java/org/openrewrite/java/trait/StringLiteralReferenceTest.java
user: login: github-actions[bot]
body: ```suggestion
/*
 * Copyright 2025 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openrewrite.java.trait;
```
created_at: 2025-04-01T13:01:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+package org.openrewrite.java.trait;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.marker.SearchResult;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+class StringLiteralReferenceTest implements RewriteTest {
+    @Override
+    public void defaults(RecipeSpec spec) {
+        spec.recipe(RewriteTest.toRecipe(() -> new StringLiteralReference.Provider().getMatcher()
+          .asVisitor(stringLiteralReference -> SearchResult.found(stringLiteralReference.getTree(), stringLiteralReference.getValue()))));
+    }
+
+    @SuppressWarnings("SpringXmlModelInspection")
+    @Test
+    @DocumentExample
+    void xmlConfiguration() {
+        rewriteRun(
+          java(
+            //language=java
+            """
+              class Test {
+                  String ref = "java.lang.String";
+              }
+              """,
+            //language=java
+            """
+              class Test {
+                  String ref = /*~~(java.lang.String)~~>*/"java.lang.String";
+              }
+              """
+          )
+        );
+    }
+}
path: rewrite-java/src/test/java/org/openrewrite/java/trait/StringLiteralReferenceTest.java
user: login: github-actions[bot]
body: ```suggestion
}
```
created_at: 2025-04-01T13:01:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+/*
+ * Copyright 2025 the original author or authors.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * https://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.trait;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.marker.SearchResult;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+class StringLiteralReferenceTest implements RewriteTest {
+    @Override
+    public void defaults(RecipeSpec spec) {
+        spec.recipe(RewriteTest.toRecipe(() -> new StringLiteralReference.Provider().getMatcher()
+          .asVisitor(stringLiteralReference -> SearchResult.found(stringLiteralReference.getTree(), stringLiteralReference.getValue()))));
+    }
+
+    @SuppressWarnings("SpringXmlModelInspection")
+    @Test
+    @DocumentExample
+    void xmlConfiguration() {
+        rewriteRun(
+          java(
+            //language=java
+            """
+              class Test {
+                  String ref = "java.lang.String";
+              }
+              """,
+            //language=java
+            """
+              class Test {
+                  String ref = /*~~(java.lang.String)~~>*/"java.lang.String";
+              }
+              """
+          )
+        );
+    }
+}
path: rewrite-java/src/test/java/org/openrewrite/java/trait/StringLiteralReferenceTest.java
user: login: github-actions[bot]
body: ```suggestion
}
```
created_at: 2025-04-02T13:22:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1477,7 +1477,7 @@ public ClassDeclaration withTypeParameters(@Nullable JContainer<TypeParameter> t
     @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
     @RequiredArgsConstructor
     @AllArgsConstructor(access = AccessLevel.PRIVATE)
-    final class CompilationUnit implements J, JavaSourceFile, SourceFile {
+    final class CompilationUnit implements J, JavaSourceFile, SourceFile, SourceFileWithReferences {
path: rewrite-java/src/main/java/org/openrewrite/java/tree/J.java
user: login: timtebeek
body: Hmm; I've seen some conditionals checking first if something is a `JavaSourceFile`, and `else` if it is a `SourceFileWithReferences`. Those would have to be adjusted if we're to continue down this path.

Alternatively just have an option on ChangeType and ChangePackage to also include String literals (default false) could be the easier option.
created_at: 2025-04-03T12:46:00Z
author_association: MEMBER
diff_hunk: @@ -91,6 +99,15 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                 stopAfterPreVisit();
                 if (tree instanceof JavaSourceFile) {
                     JavaSourceFile cu = (JavaSourceFile) tree;
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        return Traits.literal().asVisitor(lit -> {
+                            String string = lit.getString();
+                            if (string != null && string.contains(oldPackageName)) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
user: login: Laurens-W
body: Should probably include the matcher used in the visitor here too to prevent unnecessary cycles
created_at: 2025-04-04T15:13:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +105,16 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                     if (!Boolean.TRUE.equals(ignoreDefinition) && containsClassDefinition(cu, oldFullyQualifiedTypeName)) {
                         return SearchResult.found(cu);
                     }
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        return Traits.literal().asVisitor(lit -> {
+                            stopAfterPreVisit();
+                            String string = lit.getString();
+                            if (string != null && string.contains(oldFullyQualifiedTypeName)) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: Laurens-W
body: Same here
created_at: 2025-04-04T15:13:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -235,6 +252,19 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct
             return updateType(javaType);
         }
 
+        @Override
+        public J visitLiteral(J.Literal literal, ExecutionContext ctx) {
+            J.Literal lit = literal;
+            boolean visitLiterals = Boolean.TRUE.equals(ChangePackage.this.visitStringLiterals);
+            if (visitLiterals && literal.getType().equals(JavaType.Primitive.String)) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
user: login: github-actions[bot]
body: ```suggestion
            if (visitLiterals && literal.getType() == JavaType.Primitive.String) {
```
created_at: 2025-04-04T15:32:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -199,6 +216,19 @@ public J visitImport(J.Import import_, ExecutionContext ctx) {
             return updateType(javaType);
         }
 
+        @Override
+        public J visitLiteral(J.Literal literal, ExecutionContext ctx) {
+            J.Literal lit = literal;
+            boolean visitLiterals = Boolean.TRUE.equals(ChangeType.this.visitStringLiterals);
+            if (visitLiterals && literal.getType().equals(JavaType.Primitive.String)) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: github-actions[bot]
body: ```suggestion
            if (visitLiterals && literal.getType() == JavaType.Primitive.String) {
```
created_at: 2025-04-04T15:32:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +105,16 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                     if (!Boolean.TRUE.equals(ignoreDefinition) && containsClassDefinition(cu, oldFullyQualifiedTypeName)) {
                         return SearchResult.found(cu);
                     }
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        return Traits.literal().asVisitor(lit -> {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: timtebeek
body: I've pushed a new test that fails: when `visitStringLiterals = true`, but there is no String literal match, then we now also fail to replace type matches. I'd have expected this recipe to continue making type changes and not _only_ String literals. The same applies for ChangePackage, but I've not added a test there.
created_at: 2025-04-04T16:19:15Z
author_association: MEMBER
diff_hunk: @@ -97,6 +105,16 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                     if (!Boolean.TRUE.equals(ignoreDefinition) && containsClassDefinition(cu, oldFullyQualifiedTypeName)) {
                         return SearchResult.found(cu);
                     }
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        return Traits.literal().asVisitor(lit -> {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: Laurens-W
body: Fixed
created_at: 2025-04-07T15:45:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,6 +99,15 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                 stopAfterPreVisit();
                 if (tree instanceof JavaSourceFile) {
                     JavaSourceFile cu = (JavaSourceFile) tree;
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        return Traits.literal().asVisitor(lit -> {
+                            String string = lit.getString();
+                            if (string != null && string.contains(oldPackageName)) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
user: login: timtebeek
body: Not sure what you mean by this comment; could you clarify?
created_at: 2025-04-08T08:41:59Z
author_association: MEMBER
diff_hunk: @@ -91,6 +100,19 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                 stopAfterPreVisit();
                 if (tree instanceof JavaSourceFile) {
                     JavaSourceFile cu = (JavaSourceFile) tree;
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        AtomicBoolean found = new AtomicBoolean(false);
+                        Traits.literal().asVisitor(lit -> {
+                            String string = lit.getString();
+                            if (string != null && string.contains(oldPackageName)) {
+                                found.set(true);
+                            }
+                            return lit.getTree();
+                        }).visit(cu, ctx, getCursor().getParentTreeCursor());
+                        if (found.get()) {
+                            return SearchResult.found(cu);
+                        }
path: rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
user: login: timtebeek
body: Personally I tend to prefer the reduce function where possible; in this case you're only interested in the boolean, and reduce makes that explicit
```suggestion
                        if (Traits.literal().asVisitor((Literal lit, AtomicBoolean bool) -> {
                                    String string = lit.getString();
                                    if (string != null && string.contains(oldPackageName)) {
                                        bool.set(true);
                                    }
                                    return lit.getTree();
                                }).reduce(cu, new AtomicBoolean(false), getCursor().getParentTreeCursor())
                                .get()) {
                            return SearchResult.found(cu);
                        }
```
created_at: 2025-04-08T08:43:27Z
author_association: MEMBER
diff_hunk: @@ -97,6 +105,16 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                     if (!Boolean.TRUE.equals(ignoreDefinition) && containsClassDefinition(cu, oldFullyQualifiedTypeName)) {
                         return SearchResult.found(cu);
                     }
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        return Traits.literal().asVisitor(lit -> {
+                            stopAfterPreVisit();
+                            String string = lit.getString();
+                            if (string != null && string.contains(oldFullyQualifiedTypeName)) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: timtebeek
body: The `stopAfterPreVisit();` is only here and not in `ChangePackage`; any specific reason for that?

And same as above `reduce` can likely be used here as well.
created_at: 2025-04-08T08:44:14Z
author_association: MEMBER
diff_hunk: @@ -132,11 +154,10 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
         });
     }
 
-    private static class JavaChangeTypeVisitor extends JavaVisitor<ExecutionContext> {
+    private class JavaChangeTypeVisitor extends JavaVisitor<ExecutionContext> {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: timtebeek
body: Why the change to non static as opposed to expanding the constructor here? Now there's a bit of an awkward mix of passing through some outer class field values through the constructor, while directly accessing one field.
created_at: 2025-04-08T08:46:28Z
author_association: MEMBER
diff_hunk: @@ -91,6 +99,15 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                 stopAfterPreVisit();
                 if (tree instanceof JavaSourceFile) {
                     JavaSourceFile cu = (JavaSourceFile) tree;
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        return Traits.literal().asVisitor(lit -> {
+                            String string = lit.getString();
+                            if (string != null && string.contains(oldPackageName)) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
user: login: Laurens-W
body: The regex matcher used in `visitLiteral` is a little more specific on when something matches vs using contains. Using that here might save a little bit of overhead, though probably negligible
created_at: 2025-04-08T10:02:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +105,16 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
                     if (!Boolean.TRUE.equals(ignoreDefinition) && containsClassDefinition(cu, oldFullyQualifiedTypeName)) {
                         return SearchResult.found(cu);
                     }
+                    if (Boolean.TRUE.equals(visitStringLiterals)) {
+                        return Traits.literal().asVisitor(lit -> {
+                            stopAfterPreVisit();
+                            String string = lit.getString();
+                            if (string != null && string.contains(oldFullyQualifiedTypeName)) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: Laurens-W
body: Remnant of an infinite loop caused by returning the searchresult on `cu` instead of `lit.getTree`
created_at: 2025-04-08T10:02:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -132,11 +154,10 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
         });
     }
 
-    private static class JavaChangeTypeVisitor extends JavaVisitor<ExecutionContext> {
+    private class JavaChangeTypeVisitor extends JavaVisitor<ExecutionContext> {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: Laurens-W
body: I noticed other classes in this package (including `changePackage`) don't define this kind of inner visitors as static, but I agree it's not very neat. I'll revert the removal of static and add the field and adjust the constructor
created_at: 2025-04-08T10:13:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,20 +155,26 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
     private static class JavaChangeTypeVisitor extends JavaVisitor<ExecutionContext> {
         private final JavaType.Class originalType;
         private final JavaType targetType;
-
+        private final String  oldFullyQualifiedTypeName;
+        private final String  newFullyQualifiedTypeName;
 
         private J.@Nullable Identifier importAlias;
 
         @Nullable
         private final Boolean ignoreDefinition;
+        @Nullable
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: github-actions[bot]
body: ```suggestion

```
created_at: 2025-04-08T11:52:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -199,6 +224,19 @@ public J visitImport(J.Import import_, ExecutionContext ctx) {
             return updateType(javaType);
         }
 
+        @Override
+        public J visitLiteral(J.Literal literal, ExecutionContext ctx) {
+            J.Literal lit = literal;
+            if (Boolean.TRUE.equals(visitLiterals) && literal.getType() == JavaType.Primitive.String) {
+                Pattern pat = Pattern.compile("(?:\\A|\\s)" + oldFullyQualifiedTypeName + "(?:|\\s)");
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: timtebeek
body: Are we not needlessly complicating things here? I'm usually a fan of using [word boundaries with `\b`](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) , as that matches a whole category:
```suggestion
                Pattern pat = Pattern.compile("\\b" + oldFullyQualifiedTypeName + "\\b");
```
created_at: 2025-04-09T10:52:32Z
author_association: MEMBER
diff_hunk: @@ -235,6 +254,19 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct
             return updateType(javaType);
         }
 
+        @Override
+        public J visitLiteral(J.Literal literal, ExecutionContext ctx) {
+            J.Literal lit = literal;
+            if (Boolean.TRUE.equals(visitStringLiterals) && literal.getType() == JavaType.Primitive.String) {
+                Pattern pat = Pattern.compile("(?:\\A|\\s)" + oldPackageName + "[.\\p{javaJavaIdentifierStart}]*(?:|\\s)");
path: rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java
user: login: timtebeek
body: ```suggestion
                Pattern pat = Pattern.compile("\\b" + oldPackageName + "\\b");
```
created_at: 2025-04-09T10:53:12Z
author_association: MEMBER
diff_hunk: @@ -199,6 +224,19 @@ public J visitImport(J.Import import_, ExecutionContext ctx) {
             return updateType(javaType);
         }
 
+        @Override
+        public J visitLiteral(J.Literal literal, ExecutionContext ctx) {
+            J.Literal lit = literal;
+            if (Boolean.TRUE.equals(visitLiterals) && literal.getType() == JavaType.Primitive.String) {
path: rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
user: login: jevanlingen
body: ```suggestion
            if (TRUE.equals(visitLiterals) && literal.getType() == String) {
```

⚠️ Code style change, but as I start using more and more static imports. I see other people follow.
created_at: 2025-04-09T11:36:56Z
author_association: CONTRIBUTOR

PR comments:
user: login: Laurens-W
created_at: 2025-04-01T12:55:35Z
body: Tests will probably fail because I haven't added it properly to ChangeType and ChangePackage
author_association: CONTRIBUTOR
user: login: Laurens-W
created_at: 2025-04-02T14:17:56Z
body: I fear this change might be too broad as now it will by default always take String literals into account
author_association: CONTRIBUTOR