Repository: openrewrite/rewrite
Pull request #4874: Reduce the size of the match string in `TextMatches` data table
Author: login: knutwannheden
Created: 2025-01-09T14:07:43Z | Merged: 2025-05-07T15:47:32Z

When a pattern is matched against a very long line, the result in the data table is now truncated to include at most 50 characters before and 50 characters after the match of that line.



Diff:
diff --git a/rewrite-core/src/main/java/org/openrewrite/text/Find.java b/rewrite-core/src/main/java/org/openrewrite/text/Find.java
index 2b56c1aedaa..0f17469a712 100644
--- a/rewrite-core/src/main/java/org/openrewrite/text/Find.java
+++ b/rewrite-core/src/main/java/org/openrewrite/text/Find.java
@@ -97,6 +97,14 @@ public String getDescription() {
     @Nullable
     Boolean description;
 
+    @Option(displayName = "Context size for Datatable",
+            description = "The number of characters to include in the datatable before and after the match. Default `0`, " +
+                    "`-1` indicates that the whole text should be used.",
+            required = false,
+            example = "50")
+    @Nullable
+    Integer contextSize;
+
     @Override
     public String getInstanceName() {
         return String.format("Find text `%s`", find);
@@ -106,6 +114,7 @@ public String getInstanceName() {
     public TreeVisitor<?, ExecutionContext> getVisitor() {
 
         TreeVisitor<?, ExecutionContext> visitor = new TreeVisitor<Tree, ExecutionContext>() {
+
             @Override
             public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
                 SourceFile sourceFile = (SourceFile) requireNonNull(tree);
@@ -164,24 +173,41 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
                     }
 
                     int startLine = lastNewLineIndex + 1;
-                    int endLine = rawText.indexOf('\n', matcher.end());
+                    int endLine = nextNewLineIndex > matcher.end() ? nextNewLineIndex : rawText.indexOf('\n', matcher.end());
                     if (endLine == -1) {
                         endLine = rawText.length();
                     }
 
-                    //noinspection StringBufferReplaceableByString
-                    textMatches.insertRow(ctx, new TextMatches.Row(
-                            sourceFilePath,
-                            new StringBuilder(endLine - startLine + 3)
-                                    .append(rawText, startLine, matchStart)
-                                    .append("~~>")
-                                    .append(rawText, matchStart, endLine)
-                                    .toString()
-                    ));
+                    String context = truncateContext(endLine, startLine, matcher, matchStart, rawText);
+
+                    textMatches.insertRow(ctx, new TextMatches.Row(sourceFilePath, context));
                 } while (matcher.find());
                 snippets.add(snippet(rawText.substring(previousEnd)));
                 return plainText.withText("").withSnippets(snippets);
             }
+
+            private String truncateContext(int endLine, int startLine, Matcher matcher, int matchStart, String rawText) {
+                String matchText = matcher.group();
+                int contextLength = contextSize == null ? 0 : contextSize;
+                int contextStart = contextLength == -1 ? startLine : matchStart - contextLength;
+                int contextEnd = contextLength == -1 ? endLine : matchStart + matchText.length() + contextLength;
+
+                StringBuilder sb = new StringBuilder();
+
+                if (contextStart > startLine) {
+                    sb.append("...");
+                }
+
+                sb.append(rawText, Math.max(contextStart, startLine), matchStart)
+                        .append("~~>")
+                        .append(rawText, matchStart, Math.min(contextEnd, endLine));
+
+                if (contextEnd < endLine) {
+                    sb.append("...");
+                }
+
+                return sb.toString();
+            }
         };
         if (filePattern != null) {
             visitor = Preconditions.check(new FindSourceFiles(filePattern), visitor);
diff --git a/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java b/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java
index c1da4ee5533..a7ce8c52467 100644
--- a/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java
+++ b/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java
@@ -336,7 +336,7 @@ void preconditionOnNestedDeclarative() {
     void exposesUnderlyingDataTables() {
         DeclarativeRecipe dr = new DeclarativeRecipe("org.openrewrite.DeclarativeDataTable", "declarative with data table",
           "test", emptySet(), null, URI.create("dummy"), true, Collections.emptyList());
-        dr.addUninitialized(new Find("sam", null, null, null, null, null, null));
+        dr.addUninitialized(new Find("sam", null, null, null, null, null, null, null));
         dr.initialize(List.of(), Map.of());
         assertThat(dr.getDataTableDescriptors()).anyMatch(it -> "org.openrewrite.table.TextMatches".equals(it.getName()));
     }
diff --git a/rewrite-core/src/test/java/org/openrewrite/rpc/RewriteRpcTest.java b/rewrite-core/src/test/java/org/openrewrite/rpc/RewriteRpcTest.java
index d3062c10a3c..66810ecf17b 100644
--- a/rewrite-core/src/test/java/org/openrewrite/rpc/RewriteRpcTest.java
+++ b/rewrite-core/src/test/java/org/openrewrite/rpc/RewriteRpcTest.java
@@ -135,7 +135,7 @@ void runRecipe() {
             .validateRecipeSerialization(false)
             .dataTable(TextMatches.Row.class, rows -> {
                 assertThat(rows).contains(new TextMatches.Row(
-                  "hello.txt", "~~>Hello Jon!"));
+                  "hello.txt", "~~>Hello..."));
                 latch.countDown();
             }),
           text(
diff --git a/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java b/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java
index e336b6a0618..852e5e379e4 100644
--- a/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java
+++ b/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java
@@ -30,7 +30,7 @@ class FindTest implements RewriteTest {
     @Test
     void regex() {
         rewriteRun(
-          spec -> spec.recipe(new Find("[T\\s]", true, true, null, null, null, null)),
+          spec -> spec.recipe(new Find("[T\\s]", true, true, null, null, null, null, null)),
           text(
             """
               This is\ttext.
@@ -42,10 +42,30 @@ void regex() {
         );
     }
 
+    @Test
+    void isFullMatch() {
+        rewriteRun(
+          spec -> spec.recipe(new Find("This is text.", true, true, null, null, null, null, null))
+            .dataTable(TextMatches.Row.class, rows -> {
+                  assertThat(rows)
+                    .hasSize(1)
+                    .allSatisfy(r -> assertThat(r.getMatch()).isEqualTo("~~>This is text."));
+              }),
+          text(
+            """
+              This is text.
+              """,
+            """
+              ~~>This is text.
+              """
+          )
+        );
+    }
+
     @Test
     void dataTable() {
         rewriteRun(
-          spec -> spec.recipe(new Find("text", null, null, null, null, null, null))
+          spec -> spec.recipe(new Find("text", null, null, null, null, null, null, 50))
             .dataTable(TextMatches.Row.class, rows -> {
                 assertThat(rows).hasSize(1);
                 assertThat(rows.get(0).getMatch()).isEqualTo("This is ~~>text.");
@@ -68,7 +88,7 @@ void dataTable() {
     @Test
     void plainText() {
         rewriteRun(
-          spec -> spec.recipe(new Find("\\s", null, null, null, null, null, null)),
+          spec -> spec.recipe(new Find("\\s", null, null, null, null, null, null, 50)),
           text(
             """
               This i\\s text.
@@ -83,7 +103,7 @@ void plainText() {
     @Test
     void caseInsensitive() {
         rewriteRun(
-          spec -> spec.recipe(new Find("text", null, null, null, null, "**/foo/**;**/baz/**", null)),
+          spec -> spec.recipe(new Find("text", null, null, null, null, "**/foo/**;**/baz/**", null, 50)),
           dir("foo",
             text(
               """
@@ -115,7 +135,7 @@ void caseInsensitive() {
     @Test
     void regexBasicMultiLine() {
         rewriteRun(
-          spec -> spec.recipe(new Find("[T\\s]", true, true, true, null, null, null)),
+          spec -> spec.recipe(new Find("[T\\s]", true, true, true, null, null, null, 50)),
           text(
             """
               This is\ttext.
@@ -132,7 +152,7 @@ void regexBasicMultiLine() {
     @Test
     void regexWithoutMultilineAndDotall() {
         rewriteRun(
-          spec -> spec.recipe(new Find("^This.*below\\.$", true, true, false, false, null, null)),
+          spec -> spec.recipe(new Find("^This.*below\\.$", true, true, false, false, null, null, 50)),
           text(
             """
               This is text.
@@ -148,7 +168,7 @@ void regexWithoutMultilineAndDotall() {
     @Test
     void regexMatchingWhitespaceWithoutMultilineWithDotall() {
         rewriteRun(
-          spec -> spec.recipe(new Find("One.Two$", true, true, false, true, null, null)),
+          spec -> spec.recipe(new Find("One.Two$", true, true, false, true, null, null, 50)),
           //language=csv
           text( // the `.` above matches the space character on the same line
             """
@@ -163,7 +183,7 @@ void regexMatchingWhitespaceWithoutMultilineWithDotall() {
     @Test
     void regexWithoutMultilineAndWithDotAll() {
         rewriteRun(
-          spec -> spec.recipe(new Find("^This.*below\\.$", true, true, false, true, null, null)),
+          spec -> spec.recipe(new Find("^This.*below\\.$", true, true, false, true, null, null, 50)),
           text(
             """
               This is text.
@@ -186,7 +206,7 @@ void regexWithoutMultilineAndWithDotAll() {
     @Test
     void regexWithMultilineAndWithoutDotall() {
         rewriteRun(
-          spec -> spec.recipe(new Find("^This.*below\\.$", true, true, true, false, null, null)),
+          spec -> spec.recipe(new Find("^This.*below\\.$", true, true, true, false, null, null, 50)),
           text(
             """
               This is text.
@@ -209,7 +229,7 @@ void regexWithMultilineAndWithoutDotall() {
     @Test
     void regexWithBothMultilineAndDotAll() {
         rewriteRun(
-          spec -> spec.recipe(new Find("^This.*below\\.$", true, true, true, true, null, null)),
+          spec -> spec.recipe(new Find("^This.*below\\.$", true, true, true, true, null, null, 50)),
           text(
             """
               The first line.
@@ -232,7 +252,7 @@ void regexWithBothMultilineAndDotAll() {
     @Test
     void description() {
         rewriteRun(
-          spec -> spec.recipe(new Find("text", null, null, null, null, null, true)),
+          spec -> spec.recipe(new Find("text", null, null, null, null, null, true, 50)),
           text(
             """
               This is text.
@@ -243,4 +263,101 @@ void description() {
           )
         );
     }
+
+    @Test
+    void longLine() {
+        rewriteRun(
+          spec -> spec.recipe(new Find("very", null, null, null, null, null, null, 50))
+            .dataTable(TextMatches.Row.class, rows -> {
+                assertThat(rows).hasSize(18);
+                assertThat(rows).satisfiesExactly(
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("...is is a very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("...a very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, ..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very l..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very long li..."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("..., very, very, very, very, very, very, very, very, ~~>very long line.")
+                );
+            }),
+          text(
+            """
+              This is a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long line.
+              """,
+            """
+              This is a ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very long line.
+              """
+          )
+        );
+    }
+
+    @Test
+    void justMatch() {
+        rewriteRun(
+          spec -> spec.recipe(new Find("very", null, null, null, null, null, null, null))
+            .dataTable(TextMatches.Row.class, rows -> {
+                assertThat(rows).hasSize(18);
+                assertThat(rows).allSatisfy(
+                    r -> assertThat(r.getMatch()).isEqualTo("...~~>very...")
+                  );
+            }),
+          text(
+            """
+              This is a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long line.
+              """,
+            """
+              This is a ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very long line.
+              """
+          )
+        );
+    }
+
+    @Test
+    void noTruncate() {
+        rewriteRun(
+          spec -> spec.recipe(new Find("very", null, null, null, null, null, null, -1))
+            .dataTable(TextMatches.Row.class, rows -> {
+                assertThat(rows).hasSize(18);
+                assertThat(rows).satisfiesExactly(
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a ~~>very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, ~~>very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, ~~>very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, ~~>very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, very, very, very, ~~>very, very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, very, very, very, very, ~~>very, very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, very, very, very, very, very, ~~>very, very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, ~~>very, very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, ~~>very, very long line."),
+                  r -> assertThat(r.getMatch()).isEqualTo("This is a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, ~~>very long line.")
+                );
+            }),
+          text(
+            """
+              This is a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long line.
+              """,
+            """
+              This is a ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very, ~~>very long line.
+              """
+          )
+        );
+    }
 }


Reviews:
user: login: pstreef
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-01-09T14:32:18Z
user: login: sambsnyd
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T23:23:38Z
user: login: MBoegers
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T15:03:40Z
user: login: github-actions[bot]
body: Some suggestions could not be made:
* rewrite-core/src/main/java/org/openrewrite/text/Find.java
  * lines 21-21
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T09:07:12Z
user: login: timtebeek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T09:56:26Z

Review comments:
diff_hunk: @@ -101,6 +101,9 @@ public String getDescription() {
     public TreeVisitor<?, ExecutionContext> getVisitor() {
 
         TreeVisitor<?, ExecutionContext> visitor = new TreeVisitor<Tree, ExecutionContext>() {
+
+            static final int CONTEXT_SIZE = 50;
path: rewrite-core/src/main/java/org/openrewrite/text/Find.java
user: login: sambsnyd
body: Perhaps this should be configurable? Sometimes you might want long matches
created_at: 2025-04-22T23:23:38Z
author_association: MEMBER
diff_hunk: @@ -101,6 +101,9 @@ public String getDescription() {
     public TreeVisitor<?, ExecutionContext> getVisitor() {
 
         TreeVisitor<?, ExecutionContext> visitor = new TreeVisitor<Tree, ExecutionContext>() {
+
+            static final int CONTEXT_SIZE = 50;
path: rewrite-core/src/main/java/org/openrewrite/text/Find.java
user: login: MBoegers
body: I the context size is now optional configurable. The default is `0` leading to just the matched text in the Datatable. 
With `-1` the truncate can be deactivated and the full text is in the Datatable, as on main currently for whoever uses this. 
created_at: 2025-05-06T15:03:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -164,24 +174,45 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
                     }
 
                     int startLine = lastNewLineIndex + 1;
-                    int endLine = rawText.indexOf('\n', matcher.end());
+                    int endLine = nextNewLineIndex > matcher.end() ? nextNewLineIndex : rawText.indexOf('\n', matcher.end());
                     if (endLine == -1) {
                         endLine = rawText.length();
                     }
 
-                    //noinspection StringBufferReplaceableByString
-                    textMatches.insertRow(ctx, new TextMatches.Row(
-                            sourceFilePath,
-                            new StringBuilder(endLine - startLine + 3)
-                                    .append(rawText, startLine, matchStart)
-                                    .append("~~>")
-                                    .append(rawText, matchStart, endLine)
-                                    .toString()
-                    ));
+                    String context = truncateContext(endLine, startLine, matcher, matchStart, rawText);
+
+                    textMatches.insertRow(ctx, new TextMatches.Row(sourceFilePath, context));
                 } while (matcher.find());
                 snippets.add(snippet(rawText.substring(previousEnd)));
                 return plainText.withText("").withSnippets(snippets);
             }
+
+            private @NotNull String truncateContext(int endLine, int startLine, Matcher matcher, int matchStart, String rawText) {
path: rewrite-core/src/main/java/org/openrewrite/text/Find.java
user: login: github-actions[bot]
body: ```suggestion
            private String truncateContext(int endLine, int startLine, Matcher matcher, int matchStart, String rawText) {
```
created_at: 2025-05-07T09:07:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,6 +18,7 @@
 import lombok.EqualsAndHashCode;
 import lombok.Value;
 import org.apache.commons.lang3.BooleanUtils;
+import org.jetbrains.annotations.NotNull;
path: rewrite-core/src/main/java/org/openrewrite/text/Find.java
user: login: timtebeek
body: ```suggestion
```
created_at: 2025-05-07T09:56:25Z
author_association: MEMBER

PR comments:
user: login: MBoegers
created_at: 2025-05-06T15:05:22Z
body: Is it necessary to have the Search marker inside the Match text in the Datatable? if we add StartMatch and EndMatch we could have the same data without the strange looking `~~>`

EDIT: this would break existing logic and looks like not a good idea at this point.
author_association: CONTRIBUTOR
user: login: timtebeek
created_at: 2025-05-07T12:22:03Z
body: Copying the failure here: new test expectation not updated it seems.
```
RewriteRpcTest > runRecipe() FAILED
    java.lang.AssertionError: 
    Expecting ArrayList:
      [TextMatches.Row(sourcePath=hello.txt, match=...~~>Hello...)]
    to contain:
      [TextMatches.Row(sourcePath=hello.txt, match=~~>Hello Jon!)]
    but could not find the following element(s):
      [TextMatches.Row(sourcePath=hello.txt, match=~~>Hello Jon!)]
        at org.openrewrite.rpc.RewriteRpcTest.lambda$runRecipe$4(RewriteRpcTest.java:137)
        at org.openrewrite.rpc.RewriteRpcTest$$Lambda$1088/0x00007f9e4040bbc8.acceptThrows(Unknown Source)
        at org.openrewrite.test.UncheckedConsumer.accept(UncheckedConsumer.java:27)
        at org.openrewrite.test.RecipeSpec.lambda$dataTable$5(RecipeSpec.java:213)
        at org.openrewrite.test.RecipeSpec$$Lambda$833/0x00007f9e40319458.acceptThrows(Unknown Source)
        at org.openrewrite.test.UncheckedConsumer.accept(UncheckedConsumer.java:27)
        at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:388)
        at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:130)
        at org.openrewrite.rpc.RewriteRpcTest.runRecipe(RewriteRpcTest.java:131)
```
author_association: MEMBER
user: login: timtebeek
created_at: 2025-05-07T16:32:08Z
body: Please keep an eye out for downstream changes needed after these types of changes as well:
- https://github.com/openrewrite/rewrite-docker/commit/e7b3d83dd65bbd24869982391f9625ba4cba921d
author_association: MEMBER
user: login: timtebeek
created_at: 2025-05-07T16:37:03Z
body: We have a recipe that's able to add these nullable arguments as needed:
https://app.moderne.io/recipes/org.openrewrite.java.AddNullMethodArgument#defaults=W3sibmFtZSI6Im1ldGhvZFBhdHRlcm4iLCJ2YWx1ZSI6Im9yZy5vcGVucmV3cml0ZS50ZXh0LkZpbmQgPGNvbnN0cnVjdG9yPihTdHJpbmcsIEJvb2xlYW4sIEJvb2xlYW4sIEJvb2xlYW4sIEJvb2xlYW4sIFN0cmluZywgQm9vbGVhbikifSx7Im5hbWUiOiJhcmd1bWVudEluZGV4IiwidmFsdWUiOiI3In0seyJuYW1lIjoicGFyYW1ldGVyVHlwZSIsInZhbHVlIjoiamF2YS5sYW5nLkludGVnZXIifSx7Im5hbWUiOiJwYXJhbWV0ZXJOYW1lIiwidmFsdWUiOiJjb250ZXh0U2l6ZSJ9XQ==

![image](https://github.com/user-attachments/assets/2c834aba-3900-4b2e-abe1-745b77850e27)

author_association: MEMBER