Skip to content

Commit 38caf16

Browse files
Add --add-opens JVM args to surefire/failsafe for Java 25 (#1055)
* Add --add-opens JVM args to surefire/failsafe for Java 25 Java 25 enforces module boundaries more strictly, causing tests that use reflection (Mockito, Spring, Hibernate) to fail without --add-opens args. Add a new AddSurefireArgLine recipe that merges --add-opens arguments into the argLine configuration of maven-surefire-plugin and maven-failsafe-plugin, and wire it into UpgradePluginsForJava25. The recipe handles plugins with no configuration, existing configuration without argLine, and existing argLine values (merging without duplicates). Fixes moderneinc/customer-requests#1821 * Gate --add-opens flags on actual dependencies using ModuleHasDependency Split the single unconditional AddSurefireArgLine invocation into three conditional wrapper recipes gated by ModuleHasDependency preconditions: - AddSurefireArgLineForMockito: Mockito/ByteBuddy flags, gated on mockito-* - AddSurefireArgLineForSpring: Spring flags, gated on spring-* - AddSurefireArgLineForNetty: Netty flags, gated on netty-* This avoids adding unnecessary --add-opens arguments to projects that don't use those frameworks. * Remove Spring and Netty --add-opens variants, keep only Mockito * Update recipes.csv with regenerated recipe counts * Rename AddSurefireArgLine to AddSurefireFailsafeArgLine, fix copyright years and formatting - Rename recipe class to better reflect that it covers both surefire and failsafe - Update copyright year to 2026 in new files - Fix extra spaces inside parentheses in recipe implementation - Update all YAML, examples, and CSV references - Regenerate recipes.csv --------- Co-authored-by: Steve Elliott <steve@moderne.io>
1 parent bac7053 commit 38caf16

6 files changed

Lines changed: 703 additions & 6 deletions

File tree

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.openrewrite.ExecutionContext;
21+
import org.openrewrite.Option;
22+
import org.openrewrite.Recipe;
23+
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.internal.ListUtils;
25+
import org.openrewrite.maven.MavenIsoVisitor;
26+
import org.openrewrite.xml.tree.Content;
27+
import org.openrewrite.xml.tree.Xml;
28+
29+
import java.util.*;
30+
import java.util.regex.Matcher;
31+
import java.util.regex.Pattern;
32+
33+
34+
@Value
35+
@EqualsAndHashCode(callSuper = false)
36+
public class AddSurefireFailsafeArgLine extends Recipe {
37+
38+
@Option(displayName = "Arg line",
39+
description = "The arguments to add to the surefire and failsafe plugin `argLine` configuration. " +
40+
"Individual arguments are space-separated. Arguments already present in the existing argLine are not duplicated.",
41+
example = "--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED")
42+
String argLine;
43+
44+
String displayName = "Add `argLine` to surefire and failsafe plugins";
45+
46+
String description = "Adds the specified arguments to the `argLine` configuration of the Maven Surefire and Failsafe plugins, " +
47+
"merging with any existing argLine value without duplicating arguments.";
48+
49+
@Override
50+
public TreeVisitor<?, ExecutionContext> getVisitor() {
51+
return new MavenIsoVisitor<ExecutionContext>() {
52+
@Override
53+
public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
54+
Xml.Tag t = super.visitTag(tag, ctx);
55+
if (!isPluginTag(t)) {
56+
return t;
57+
}
58+
String artifactId = t.getChildValue("artifactId").orElse("");
59+
if (!"maven-surefire-plugin".equals(artifactId) && !"maven-failsafe-plugin".equals(artifactId)) {
60+
return t;
61+
}
62+
String groupId = t.getChildValue("groupId").orElse("org.apache.maven.plugins");
63+
if (!"org.apache.maven.plugins".equals(groupId)) {
64+
return t;
65+
}
66+
67+
Optional<Xml.Tag> configTag = t.getChild("configuration");
68+
if (configTag.isPresent()) {
69+
Xml.Tag config = configTag.get();
70+
Optional<Xml.Tag> argLineTag = config.getChild("argLine");
71+
if (argLineTag.isPresent()) {
72+
String existingValue = argLineTag.get().getValue().orElse( "" );
73+
String merged = mergeArgLine( existingValue, argLine );
74+
if (merged.equals( existingValue )) {
75+
return t;
76+
}
77+
// Update argLine value in-place to preserve formatting
78+
Xml.Tag updatedArgLine = argLineTag.get().withValue( merged );
79+
Xml.Tag updatedConfig = config.withContent( ListUtils.map( (List<Content>) config.getContent(), c ->
80+
c == argLineTag.get() ? updatedArgLine : c ) );
81+
return t.withContent( ListUtils.map( (List<Content>) t.getContent(), c ->
82+
c == config ? updatedConfig : c ) );
83+
}
84+
Xml.Tag newArgLine = Xml.Tag.build( "<argLine>" + argLine + "</argLine>" );
85+
Xml.Tag updatedConfig = config.withContent(
86+
ListUtils.concat( newArgLine, (List<Content>) config.getContent() ) );
87+
t = t.withContent( ListUtils.map( (List<Content>) t.getContent(), c ->
88+
c == config ? updatedConfig : c ) );
89+
return autoFormat( t, ctx );
90+
}
91+
Xml.Tag newConfig = Xml.Tag.build(
92+
"<configuration>\n<argLine>" + argLine + "</argLine>\n</configuration>" );
93+
t = t.withContent( ListUtils.concat( (List<Content>) t.getContent(), newConfig ) );
94+
return autoFormat( t, ctx );
95+
}
96+
97+
private boolean isPluginTag(Xml.Tag tag) {
98+
return "plugin".equals(tag.getName()) &&
99+
getCursor().getParentTreeCursor().getValue() instanceof Xml.Tag &&
100+
"plugins".equals(((Xml.Tag) getCursor().getParentTreeCursor().getValue()).getName());
101+
}
102+
};
103+
}
104+
105+
private static final Pattern ARG_PATTERN = Pattern.compile("(--add-opens\\s+\\S+|-\\S+(?:\\s+(?!-)\\S+)*)");
106+
107+
static String mergeArgLine(String existing, String toAdd) {
108+
// Parse compound args like "--add-opens module/pkg=target" as single units
109+
Set<String> existingArgs = parseArgs(existing);
110+
List<String> argsToAdd = new ArrayList<>();
111+
Matcher m = ARG_PATTERN.matcher(toAdd);
112+
while (m.find()) {
113+
String arg = m.group(1).trim();
114+
// Normalize internal whitespace for comparison
115+
String normalized = arg.replaceAll("\\s+", " ");
116+
if (!existingArgs.contains(normalized)) {
117+
argsToAdd.add(normalized);
118+
existingArgs.add(normalized);
119+
}
120+
}
121+
if (argsToAdd.isEmpty()) {
122+
return existing;
123+
}
124+
StringBuilder result = new StringBuilder(existing);
125+
for (String arg : argsToAdd) {
126+
result.append(' ').append(arg);
127+
}
128+
return result.toString();
129+
}
130+
131+
private static Set<String> parseArgs(String argLine) {
132+
Set<String> args = new LinkedHashSet<>();
133+
Matcher m = ARG_PATTERN.matcher(argLine);
134+
while (m.find()) {
135+
args.add(m.group(1).replaceAll("\\s+", " ").trim());
136+
}
137+
// Also add individual tokens for property references like ${argLine}
138+
for (String token : argLine.split("\\s+")) {
139+
if (!token.isEmpty() && !token.startsWith("-")) {
140+
args.add(token);
141+
}
142+
}
143+
return args;
144+
}
145+
}

src/main/resources/META-INF/rewrite/examples.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,51 @@ examples:
205205
language: java
206206
---
207207
type: specs.openrewrite.org/v1beta/example
208+
recipeName: org.openrewrite.java.migrate.AddSurefireFailsafeArgLine
209+
examples:
210+
- description: '`AddSurefireFailsafeArgLineTest#surefireWithNoConfiguration`'
211+
parameters:
212+
- ARG_LINE
213+
sources:
214+
- before: project
215+
language: mavenProject
216+
- before: |
217+
<project>
218+
<groupId>com.mycompany.app</groupId>
219+
<artifactId>my-app</artifactId>
220+
<version>1</version>
221+
<build>
222+
<plugins>
223+
<plugin>
224+
<groupId>org.apache.maven.plugins</groupId>
225+
<artifactId>maven-surefire-plugin</artifactId>
226+
<version>3.5.2</version>
227+
</plugin>
228+
</plugins>
229+
</build>
230+
</project>
231+
after: |
232+
<project>
233+
<groupId>com.mycompany.app</groupId>
234+
<artifactId>my-app</artifactId>
235+
<version>1</version>
236+
<build>
237+
<plugins>
238+
<plugin>
239+
<groupId>org.apache.maven.plugins</groupId>
240+
<artifactId>maven-surefire-plugin</artifactId>
241+
<version>3.5.2</version>
242+
<configuration>
243+
<argLine>--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED</argLine>
244+
</configuration>
245+
</plugin>
246+
</plugins>
247+
</build>
248+
</project>
249+
path: pom.xml
250+
language: xml
251+
---
252+
type: specs.openrewrite.org/v1beta/example
208253
recipeName: org.openrewrite.java.migrate.ArrayStoreExceptionToTypeNotPresentException
209254
examples:
210255
- description: '`ArrayStoreExceptionToTypeNotPresentExceptionTest#replaceCaughtException`'
@@ -8184,6 +8229,8 @@ recipeName: org.openrewrite.java.migrate.lombok.LombokBestPractices
81848229
examples:
81858230
- description: '`LombokBestPracticesTest#providedScope`'
81868231
sources:
8232+
- before: project
8233+
language: mavenProject
81878234
- before: |
81888235
<project>
81898236
<modelVersion>4.0.0</modelVersion>

src/main/resources/META-INF/rewrite/java-version-25.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,27 @@ recipeList:
221221
groupId: org.mockito
222222
artifactId: mockito-*
223223
newVersion: 5.17.x
224+
- org.openrewrite.java.migrate.AddSurefireFailsafeArgLineForMockito
225+
226+
---
227+
type: specs.openrewrite.org/v1beta/recipe
228+
name: org.openrewrite.java.migrate.AddSurefireFailsafeArgLineForMockito
229+
displayName: Add surefire `--add-opens` for Mockito/ByteBuddy
230+
description: >-
231+
Adds `--add-opens` JVM arguments required by Mockito and ByteBuddy to the Maven Surefire and Failsafe plugin
232+
`argLine` configuration. Only applied when the project depends on Mockito.
233+
preconditions:
234+
- org.openrewrite.java.dependencies.search.ModuleHasDependency:
235+
groupIdPattern: org.mockito
236+
artifactIdPattern: mockito-*
237+
recipeList:
238+
- org.openrewrite.java.migrate.AddSurefireFailsafeArgLine:
239+
argLine: >-
240+
--add-opens java.base/java.lang=ALL-UNNAMED
241+
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
242+
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
243+
--add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
244+
-Dnet.bytebuddy.experimental=true
224245
225246
---
226247
type: specs.openrewrite.org/v1beta/recipe

0 commit comments

Comments
 (0)