diff --git a/build.gradle.kts b/build.gradle.kts
index fb0e89dd79..a767ea7cbb 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -25,6 +25,7 @@ recipeDependencies {
testParserClasspath("jakarta.faces:jakarta.faces-api:4.0.1")
testParserClasspath("jakarta.servlet:jakarta.servlet-api:4.0.2")
testParserClasspath("jakarta.servlet:jakarta.servlet-api:5.0.0")
+ testParserClasspath("jakarta.jws:jakarta.jws-api:3.0.0")
testParserClasspath("jakarta.ws.rs:jakarta.ws.rs-api:3.1.0")
testParserClasspath("jakarta.xml.soap:jakarta.xml.soap-api:2.0.1")
testParserClasspath("javax.enterprise:cdi-api:2.0.SP1")
@@ -93,6 +94,7 @@ dependencies {
testRuntimeOnly("com.fasterxml.jackson.module:jackson-module-jaxb-annotations")
testRuntimeOnly("commons-logging:commons-logging:1.3.2")
testRuntimeOnly("org.apache.logging.log4j:log4j-api:2.23.1")
+ testRuntimeOnly("jakarta.jws:jakarta.jws-api:3.0.0")
testRuntimeOnly("org.apache.johnzon:johnzon-core:1.2.18")
testRuntimeOnly("org.apache.groovy:groovy:4.+")
testRuntimeOnly("org.jboss.logging:jboss-logging:3.6.0.Final")
diff --git a/src/main/resources/META-INF/rewrite/jakarta-ee-10.yml b/src/main/resources/META-INF/rewrite/jakarta-ee-10.yml
index fc4a254cb3..2bec00b2b6 100644
--- a/src/main/resources/META-INF/rewrite/jakarta-ee-10.yml
+++ b/src/main/resources/META-INF/rewrite/jakarta-ee-10.yml
@@ -509,6 +509,7 @@ recipeList:
version: 4.0.x
onlyIfUsing: jakarta.jws..*
acceptTransitive: true
+
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.migrate.jakarta.UpdateJerseyDependencies
diff --git a/src/test/java/org/openrewrite/java/migrate/jakarta/ReplaceJakartaJwsWithJakartaXmlWsTest.java b/src/test/java/org/openrewrite/java/migrate/jakarta/ReplaceJakartaJwsWithJakartaXmlWsTest.java
index 95da9d1257..034ddffadd 100644
--- a/src/test/java/org/openrewrite/java/migrate/jakarta/ReplaceJakartaJwsWithJakartaXmlWsTest.java
+++ b/src/test/java/org/openrewrite/java/migrate/jakarta/ReplaceJakartaJwsWithJakartaXmlWsTest.java
@@ -17,10 +17,14 @@
import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
+import org.openrewrite.java.JavaParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.java.Assertions.mavenProject;
+import static org.openrewrite.java.Assertions.srcMainJava;
import static org.openrewrite.maven.Assertions.pomXml;
class ReplaceJakartaJwsWithJakartaXmlWsTest implements RewriteTest {
@@ -61,6 +65,82 @@ void replacesJakartaJwsApiInPlaceWithJakartaXmlWsApi() {
);
}
+ @Test
+ void avoidDuplicateWsApi() {
+ rewriteRun(
+ //language=xml
+ pomXml(
+ """
+
+ 4.0.0
+ com.example
+ demo
+ 0.0.1-SNAPSHOT
+
+
+ jakarta.jws
+ jakarta.jws-api
+ 3.0.0
+
+
+ jakarta.xml.ws
+ jakarta.xml.ws-api
+ 3.0.1
+
+
+
+ """,
+ spec -> spec.after(pom -> assertThat(pom)
+ .doesNotContain("jakarta.jws")
+ // Verify only one entry of `jakarta.xml.ws-api` exists
+ .containsOnlyOnce("jakarta.xml.ws-api")
+ // Verify the dependency was upgraded to a 4.x version, without pinning the exact patch
+ .contains("4")
+ .actual())
+ )
+ );
+ }
+
+ @Test
+ void addsJakartaXmlWsApiWhenJwsIsOnlyTransitive() {
+ rewriteRun(
+ spec -> spec.parser(JavaParser.fromJavaVersion().classpath("jakarta.jws-api")),
+ mavenProject(
+ "demo",
+ //language=java
+ srcMainJava(
+ java(
+ """
+ import jakarta.jws.WebService;
+
+ @WebService
+ public class HelloService {
+ public String sayHello(String name) {
+ return "Hello, " + name;
+ }
+ }
+ """
+ )
+ ),
+ //language=xml
+ pomXml(
+ """
+
+ 4.0.0
+ com.example
+ demo
+ 0.0.1-SNAPSHOT
+
+ """,
+ spec -> spec.after(pom -> assertThat(pom)
+ .containsOnlyOnce("jakarta.xml.ws-api")
+ .contains("4")
+ .actual())
+ )
+ )
+ );
+ }
+
@Test
void noChangeWhenJakartaJwsApiNotPresent() {
rewriteRun(