diff --git a/build.gradle.kts b/build.gradle.kts index d280a73a8f..d187ac145b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ recipeDependencies { testParserClasspath("com.github.spotbugs:spotbugs-annotations:4.9.3") testParserClasspath("com.sun.faces:jsf-api:2.1.29-11") testParserClasspath("com.sun.istack:istack-commons-runtime:4.1.2") + testParserClasspath("com.twilio.sdk:twilio:7.55.3") testParserClasspath("commons-fileupload:commons-fileupload:1.5") testParserClasspath("commons-logging:commons-logging:1.3.2") testParserClasspath("io.micrometer:micrometer-core:1.15.4") @@ -51,6 +52,7 @@ recipeDependencies { testParserClasspath("javax.xml.bind:jaxb-api:2.3.1") testParserClasspath("org.apache.commons:commons-fileupload2-jakarta:2.0.0-M1") testParserClasspath("org.apache.commons:commons-fileupload2-jakarta-servlet5:2.0.0-M5") + testParserClasspath("org.apache.httpcomponents:httpclient:4.5.14") testParserClasspath("org.apache.johnzon:johnzon-core:1.2.18") testParserClasspath("org.apache.logging.log4j:log4j-api:2.23.1") testParserClasspath("org.apache.ws.security:wss4j:1.6.19") diff --git a/src/main/java/org/openrewrite/java/migrate/twilio/TwilioJwtSecretToByteArray.java b/src/main/java/org/openrewrite/java/migrate/twilio/TwilioJwtSecretToByteArray.java new file mode 100644 index 0000000000..e2122ed3a1 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/twilio/TwilioJwtSecretToByteArray.java @@ -0,0 +1,121 @@ +/* + * Copyright 2026 the original author or authors. + *
+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * https://docs.moderne.io/licensing/moderne-source-available-license + *
+ * 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.migrate.twilio;
+
+import lombok.Getter;
+import org.openrewrite.Cursor;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.SourceFile;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.internal.ListUtils;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.MethodMatcher;
+import org.openrewrite.java.search.UsesMethod;
+import org.openrewrite.java.tree.Expression;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JContainer;
+import org.openrewrite.java.tree.JavaType;
+
+public class TwilioJwtSecretToByteArray extends Recipe {
+ private static final MethodMatcher ACCESS_TOKEN_BUILDER =
+ new MethodMatcher("com.twilio.jwt.accesstoken.AccessToken$Builder
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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.
+ */
+@NullMarked
+@NonNullFields
+package org.openrewrite.java.migrate.twilio;
+
+import org.jspecify.annotations.NullMarked;
+import org.openrewrite.internal.lang.NonNullFields;
diff --git a/src/main/resources/META-INF/rewrite/category.yml b/src/main/resources/META-INF/rewrite/category.yml
index c502367b0d..bfb8ffb91e 100644
--- a/src/main/resources/META-INF/rewrite/category.yml
+++ b/src/main/resources/META-INF/rewrite/category.yml
@@ -90,3 +90,8 @@ type: specs.openrewrite.org/v1beta/category
name: DataNucleus
packageName: org.openrewrite.java.migrate.datanucleus
description: Recipes for migrating [DataNucleus](https://www.datanucleus.org/) JDO/JPA persistence applications.
+---
+type: specs.openrewrite.org/v1beta/category
+name: Twilio
+packageName: org.openrewrite.java.migrate.twilio
+description: Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).
diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv
index 783c5bc0be..0501de2e7c 100644
--- a/src/main/resources/META-INF/rewrite/recipes.csv
+++ b/src/main/resources/META-INF/rewrite/recipes.csv
@@ -542,6 +542,17 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.s
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.search.PlanJavaMigration,Plan a Java version migration,Study the set of Java versions and associated tools in use across many repositories.,1,,Search,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.migrate.table.JavaVersionMigrationPlan"",""displayName"":""Java version migration plan"",""instanceName"":""Java version migration plan"",""description"":""A per-repository view of the current state of Java versions and associated build tools"",""columns"":[{""name"":""hasJava"",""type"":""boolean"",""displayName"":""Has Java"",""description"":""Whether this is a Java repository at all.""},{""name"":""sourceCompatibility"",""type"":""String"",""displayName"":""Source compatibility"",""description"":""The source compatibility of the source file.""},{""name"":""majorVersionSourceCompatibility"",""type"":""Integer"",""displayName"":""Major version source compatibility"",""description"":""The major version.""},{""name"":""targetCompatibility"",""type"":""String"",""displayName"":""Target compatibility"",""description"":""The target compatibility or `--release` version of the source file.""},{""name"":""gradleVersion"",""type"":""String"",""displayName"":""Gradle version"",""description"":""The version of Gradle in use, if any.""},{""name"":""hasGradleBuild"",""type"":""Boolean"",""displayName"":""Has Gradle build"",""description"":""Whether a `build.gradle` file exists in the repository.""},{""name"":""mavenVersion"",""type"":""String"",""displayName"":""Maven version"",""description"":""The version of Maven in use, if any.""},{""name"":""hasMavenPom"",""type"":""Boolean"",""displayName"":""Has Maven pom"",""description"":""Whether a `pom.xml` file exists in the repository.""}]}]"
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.sql.JavaSqlAPIs,Use modernized `java.sql` APIs,"Certain Java sql APIs have become deprecated and their usages changed, necessitating usage changes.",2,,`java.sql` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.sql.MigrateDriverManagerSetLogStream,Use `DriverManager#setLogWriter(java.io.PrintWriter)`,Use `DriverManager#setLogWriter(java.io.PrintWriter)` instead of the deprecated `DriverManager#setLogStream(java.io.PrintStream)` in Java 1.2 or higher.,1,,`java.sql` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioDroppedDependencies,Declare the dependencies the Twilio Java SDK no longer brings along,"Twilio Java SDK 7.x brought Guava, Joda-Time and Apache HttpClient 4 to the projects using it, which can use them without declaring them. Later versions do not, so this declares the ones a project uses and no longer gets any other way. A version the project declares itself is left as it is.",7,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioDroppedGuava,Declare the Guava the Twilio Java SDK no longer brings along,"Adds `com.google.guava:guava` to a project that uses it but does not declare it, as `AddDependency` would otherwise raise a version the project does declare.",2,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioDroppedHttpClient,Declare the Apache HttpClient 4 the Twilio Java SDK no longer brings along,"Adds `org.apache.httpcomponents:httpclient` to a project that uses it but does not declare it, as `AddDependency` would otherwise raise a version the project does declare.",2,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioDroppedJodaTime,Declare the Joda-Time the Twilio Java SDK no longer brings along,"Adds `joda-time:joda-time` to a project that uses it but does not declare it, as `AddDependency` would otherwise raise a version the project does declare.",2,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioJwtSecretToByteArray,Pass Twilio JWT secrets as `byte[]`,"Twilio Java SDK 10.8.0 changed the secret of `AccessToken.Builder`, `ClientCapability.Builder` and `TaskRouterCapability.Builder` from `String` to `byte[]`. The SDK used to call `String#getBytes()` itself, so this calls it at the construction site, with `StandardCharsets.UTF_8` rather than the platform default. Twilio secrets are ASCII, so the bytes and the signatures made with them stay the same.",1,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioNetworkHttpClientBuilder,Pass `NetworkHttpClient` an Apache HttpClient 5 builder,"Twilio Java SDK 11.0.0 moved `NetworkHttpClient` from Apache HttpClient 4 to 5, so its constructor takes the HttpClient 5 `HttpClientBuilder`. This changes the builder's type only in the source files that pass one to `NetworkHttpClient`, as other uses of Apache HttpClient 4 are not part of this migration.",2,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioRenamedApis,Use the Twilio APIs renamed in 8.0.0,"Twilio Java SDK 8.0.0 renamed the nested TwiML builder methods after their TwiML element, and removed the deprecated `ConversationsGrant` and `configurationProfileSid` of `VideoGrant`, whose replacements are `VideoGrant` and `room`.",15,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioUnusedAuthyImports,Remove unused imports of Twilio Authy,"Authy was removed from the Twilio Java SDK, so an import of it no longer compiles even when nothing uses it. A use of Authy has no replacement, so it is left for the compiler to point out.",2,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioUnusedAutopilotImports,Remove unused imports of Twilio Autopilot,"Autopilot was removed from the Twilio Java SDK, so an import of it no longer compiles even when nothing uses it. A use of Autopilot has no replacement, so it is left for the compiler to point out.",2,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.TwilioUnusedFaxImports,Remove unused imports of Twilio Fax,"Fax was removed from the Twilio Java SDK, so an import of it no longer compiles even when nothing uses it. A use of Fax has no replacement, so it is left for the compiler to point out.",2,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.twilio.UpgradeTwilio_13,Migrate to Twilio Java SDK 13,"Migrate applications from the Twilio Java SDK `com.twilio.sdk:twilio` 7.x or later to 13.x, which needs Java 8 or later. This raises the dependency, declares what the SDK no longer brings along, and changes the calls that have a mechanical replacement. A Gradle project with dependency locking needs its `gradle.lockfile` written again. Calls that need a decision are left for the compiler to point out: the `org.joda.time` values the resources returned are `java.time` since 8.0.0, `ListenableFuture` is `CompletableFuture` in the `*Async` methods, the Guava `Range` date filters are separate before and after setters, and `getPrice()` of `Message` and `Call` is a `String`. The Authy, Autopilot, Fax and Preview resources were removed with no replacement, and a newer JJWT comes along with the SDK, which removed `Jwts.parserBuilder()` for code that uses JJWT itself. No compiler reports these, which appear at runtime: JJWT 0.12 refuses an HMAC secret shorter than 256 bits, which a real Twilio secret is not but a placeholder in a test may be; a mocked `TwilioRestClient` needs `getAccountSid()` stubbed, as the resources no longer tolerate it being `null`; and `Message.fromJson` given a plain `ObjectMapper` needs `JavaTimeModule` on Java 17 or later with an old Jackson such as the 2.9 of Spring Boot 2.1, which the mapper of a real `TwilioRestClient` already has.",33,,Twilio,Modernize,Java,,Recipes for migrating applications that use the [Twilio Java SDK](https://github.com/twilio/twilio-java).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.IteratorNext,Replace `iterator().next()` with `getFirst()`,Replace `SequencedCollection.iterator().next()` with `getFirst()`.,1,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.JavaUtilAPIs,Use modernized `java.util` APIs,Certain java util APIs have been introduced and are favored over previous APIs.,25,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.util.ListFirstAndLast,"Replace `List.get(int)`, `add(int, Object)`, and `remove(int)` with `SequencedCollection` `*First` and `*Last` methods","Replace `list.get(0)` with `list.getFirst()`, `list.get(list.size() - 1)` with `list.getLast()`, and similar for `add(int, E)` and `remove(int)`.",1,,`java.util` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,
diff --git a/src/main/resources/META-INF/rewrite/twilio.yml b/src/main/resources/META-INF/rewrite/twilio.yml
new file mode 100644
index 0000000000..541227d501
--- /dev/null
+++ b/src/main/resources/META-INF/rewrite/twilio.yml
@@ -0,0 +1,232 @@
+#
+# Copyright 2026 the original author or authors.
+#
+# Licensed under the Moderne Source Available License (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://docs.moderne.io/licensing/moderne-source-available-license
+#
+# 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.
+#
+
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.migrate.twilio.UpgradeTwilio_13
+displayName: Migrate to Twilio Java SDK 13
+description: >-
+ Migrate applications from the Twilio Java SDK `com.twilio.sdk:twilio` 7.x or later to 13.x, which needs Java 8 or
+ later. This raises the dependency, declares what the SDK no longer brings along, and changes the calls that have a
+ mechanical replacement. A Gradle project with dependency locking needs its `gradle.lockfile` written again. Calls
+ that need a decision are left for the compiler to point out: the `org.joda.time` values the resources returned
+ are `java.time` since 8.0.0, `ListenableFuture` is `CompletableFuture` in the `*Async` methods, the Guava `Range`
+ date filters are separate before and after setters, and `getPrice()` of `Message` and `Call` is a `String`. The
+ Authy, Autopilot, Fax and Preview resources were removed with no replacement, and a newer JJWT comes along with the
+ SDK, which removed `Jwts.parserBuilder()` for code that uses JJWT itself. No compiler reports these, which
+ appear at runtime: JJWT 0.12 refuses an HMAC secret shorter than 256 bits, which a real Twilio secret is not but a
+ placeholder in a test may be; a mocked `TwilioRestClient` needs `getAccountSid()` stubbed, as the resources no
+ longer tolerate it being `null`; and `Message.fromJson` given a plain `ObjectMapper` needs `JavaTimeModule` on Java
+ 17 or later with an old Jackson such as the 2.9 of Spring Boot 2.1, which the mapper of a real `TwilioRestClient`
+ already has.
+tags:
+ - twilio
+recipeList:
+ - org.openrewrite.java.migrate.twilio.TwilioRenamedApis
+ - org.openrewrite.java.migrate.twilio.TwilioJwtSecretToByteArray
+ - org.openrewrite.java.migrate.twilio.TwilioNetworkHttpClientBuilder
+ - org.openrewrite.java.migrate.twilio.TwilioUnusedAutopilotImports
+ - org.openrewrite.java.migrate.twilio.TwilioUnusedAuthyImports
+ - org.openrewrite.java.migrate.twilio.TwilioUnusedFaxImports
+ - org.openrewrite.java.dependencies.UpgradeDependencyVersion:
+ groupId: com.twilio.sdk
+ artifactId: twilio
+ newVersion: 13.x
+ - org.openrewrite.java.migrate.twilio.TwilioDroppedDependencies
+
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.migrate.twilio.TwilioRenamedApis
+displayName: Use the Twilio APIs renamed in 8.0.0
+description: >-
+ Twilio Java SDK 8.0.0 renamed the nested TwiML builder methods after their TwiML element, and removed the deprecated
+ `ConversationsGrant` and `configurationProfileSid` of `VideoGrant`, whose replacements are `VideoGrant` and `room`.
+recipeList:
+ - org.openrewrite.java.ChangeType:
+ oldFullyQualifiedTypeName: com.twilio.jwt.accesstoken.ConversationsGrant
+ newFullyQualifiedTypeName: com.twilio.jwt.accesstoken.VideoGrant
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.jwt.accesstoken.VideoGrant getConfigurationProfileSid()
+ newMethodName: getRoom
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.jwt.accesstoken.VideoGrant setConfigurationProfileSid(String)
+ newMethodName: setRoom
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Refer$Builder referSip(com.twilio.twiml.voice.ReferSip)
+ newMethodName: sip
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlBreak(com.twilio.twiml.voice.SsmlBreak)
+ newMethodName: break_
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlEmphasis(com.twilio.twiml.voice.SsmlEmphasis)
+ newMethodName: emphasis
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlLang(com.twilio.twiml.voice.SsmlLang)
+ newMethodName: lang
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlP(com.twilio.twiml.voice.SsmlP)
+ newMethodName: p
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlPhoneme(com.twilio.twiml.voice.SsmlPhoneme)
+ newMethodName: phoneme
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlProsody(com.twilio.twiml.voice.SsmlProsody)
+ newMethodName: prosody
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlS(com.twilio.twiml.voice.SsmlS)
+ newMethodName: s
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlSayAs(com.twilio.twiml.voice.SsmlSayAs)
+ newMethodName: sayAs
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlSub(com.twilio.twiml.voice.SsmlSub)
+ newMethodName: sub
+ - org.openrewrite.java.ChangeMethodName:
+ methodPattern: com.twilio.twiml.voice.Say$Builder ssmlW(com.twilio.twiml.voice.SsmlW)
+ newMethodName: w
+
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.migrate.twilio.TwilioNetworkHttpClientBuilder
+displayName: Pass `NetworkHttpClient` an Apache HttpClient 5 builder
+description: >-
+ Twilio Java SDK 11.0.0 moved `NetworkHttpClient` from Apache HttpClient 4 to 5, so its constructor takes the
+ HttpClient 5 `HttpClientBuilder`. This changes the builder's type only in the source files that pass one to
+ `NetworkHttpClient`, as other uses of Apache HttpClient 4 are not part of this migration.
+preconditions:
+ - org.openrewrite.java.search.FindMethods:
+ methodPattern: com.twilio.http.NetworkHttpClient
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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.migrate.twilio;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.InMemoryExecutionContext;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.kotlin.KotlinParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.kotlin.Assertions.kotlin;
+
+class TwilioJwtSecretToByteArrayTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new TwilioJwtSecretToByteArray())
+ .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "twilio"));
+ }
+
+ @DocumentExample
+ @Test
+ void accessTokenSecret() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ class Tokens {
+ String mint(String accountSid, String apiKey, String apiSecret) {
+ return new AccessToken.Builder(accountSid, apiKey, apiSecret).identity("alice").build().toJwt();
+ }
+ }
+ """,
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ String mint(String accountSid, String apiKey, String apiSecret) {
+ return new AccessToken.Builder(accountSid, apiKey, apiSecret.getBytes(StandardCharsets.UTF_8)).identity("alice").build().toJwt();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void clientCapabilityAuthToken() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.client.ClientCapability;
+
+ class Tokens {
+ ClientCapability capability(String accountSid, String authToken) {
+ return new ClientCapability.Builder(accountSid, authToken).build();
+ }
+ }
+ """,
+ """
+ import com.twilio.jwt.client.ClientCapability;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ ClientCapability capability(String accountSid, String authToken) {
+ return new ClientCapability.Builder(accountSid, authToken.getBytes(StandardCharsets.UTF_8)).build();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void taskRouterCapabilityAuthTokenIsNotTheLastArgument() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.taskrouter.TaskRouterCapability;
+
+ class Tokens {
+ TaskRouterCapability capability(String accountSid, String authToken, String workspaceSid, String channelId) {
+ return new TaskRouterCapability.Builder(accountSid, authToken, workspaceSid, channelId).build();
+ }
+ }
+ """,
+ """
+ import com.twilio.jwt.taskrouter.TaskRouterCapability;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ TaskRouterCapability capability(String accountSid, String authToken, String workspaceSid, String channelId) {
+ return new TaskRouterCapability.Builder(accountSid, authToken.getBytes(StandardCharsets.UTF_8), workspaceSid, channelId).build();
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void keepsTheLayoutOfArgumentsOnTheirOwnLines() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ class Tokens {
+ AccessToken.Builder builder() {
+ return new AccessToken.Builder(
+ System.getProperty("ACCOUNT_SID"),
+ System.getProperty("API_KEY"),
+ System.getProperty("API_SECRET")
+ ).identity("alice");
+ }
+ }
+ """,
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ AccessToken.Builder builder() {
+ return new AccessToken.Builder(
+ System.getProperty("ACCOUNT_SID"),
+ System.getProperty("API_KEY"),
+ System.getProperty("API_SECRET").getBytes(StandardCharsets.UTF_8)
+ ).identity("alice");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void concatenationKeepsItsPrecedence() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ class Tokens {
+ AccessToken.Builder builder(String sid, String key, String head, String tail) {
+ return new AccessToken.Builder(sid, key, head + tail);
+ }
+ }
+ """,
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ AccessToken.Builder builder(String sid, String key, String head, String tail) {
+ return new AccessToken.Builder(sid, key, (head + tail).getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void ternaryKeepsItsPrecedence() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ class Tokens {
+ AccessToken.Builder builder(String sid, String key, boolean live, String a, String b) {
+ return new AccessToken.Builder(sid, key, live ? a : b);
+ }
+ }
+ """,
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ AccessToken.Builder builder(String sid, String key, boolean live, String a, String b) {
+ return new AccessToken.Builder(sid, key, (live ? a : b).getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void methodCallAndLiteralNeedNoParentheses() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ class Tokens {
+ AccessToken.Builder fromEnvironment(String sid, String key) {
+ return new AccessToken.Builder(sid, key, System.getenv("TWILIO_API_SECRET"));
+ }
+
+ AccessToken.Builder fromLiteral(String sid, String key) {
+ return new AccessToken.Builder(sid, key, "abcdefghijklmnopqrstuvwxyz012345");
+ }
+ }
+ """,
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ AccessToken.Builder fromEnvironment(String sid, String key) {
+ return new AccessToken.Builder(sid, key, System.getenv("TWILIO_API_SECRET").getBytes(StandardCharsets.UTF_8));
+ }
+
+ AccessToken.Builder fromLiteral(String sid, String key) {
+ return new AccessToken.Builder(sid, key, "abcdefghijklmnopqrstuvwxyz012345".getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void reusesAnExistingImport() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ byte[] utf8 = "x".getBytes(StandardCharsets.UTF_8);
+
+ AccessToken.Builder builder(String sid, String key, String secret) {
+ return new AccessToken.Builder(sid, key, secret);
+ }
+ }
+ """,
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ import java.nio.charset.StandardCharsets;
+
+ class Tokens {
+ byte[] utf8 = "x".getBytes(StandardCharsets.UTF_8);
+
+ AccessToken.Builder builder(String sid, String key, String secret) {
+ return new AccessToken.Builder(sid, key, secret.getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void nullSecretCompilesAsItIs() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import com.twilio.jwt.accesstoken.AccessToken;
+
+ class Tokens {
+ AccessToken.Builder builder(String sid, String key) {
+ return new AccessToken.Builder(sid, key, null);
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void kotlinIsLeftForTheCompilerToPointOut() {
+ rewriteRun(
+ spec -> spec.parser(KotlinParser.builder().classpathFromResources(new InMemoryExecutionContext(), "twilio")),
+ //language=kotlin
+ kotlin(
+ """
+ import com.twilio.jwt.accesstoken.AccessToken
+
+ fun builder(sid: String, key: String, secret: String): AccessToken.Builder {
+ return AccessToken.Builder(sid, key, secret)
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void otherConstructorsAreLeftAlone() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ class Builder {
+ Builder(String accountSid, String keySid, String secret) {
+ }
+ }
+
+ class Tokens {
+ Builder builder(String sid, String key, String secret) {
+ return new Builder(sid, key, secret);
+ }
+ }
+ """
+ )
+ );
+ }
+}
diff --git a/src/test/java/org/openrewrite/java/migrate/twilio/UpgradeTwilio13Test.java b/src/test/java/org/openrewrite/java/migrate/twilio/UpgradeTwilio13Test.java
new file mode 100644
index 0000000000..f858aaad1f
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/migrate/twilio/UpgradeTwilio13Test.java
@@ -0,0 +1,800 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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.migrate.twilio;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.InMemoryExecutionContext;
+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.gradle.Assertions.buildGradle;
+import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi;
+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 UpgradeTwilio13Test implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipeFromResources("org.openrewrite.java.migrate.twilio.UpgradeTwilio_13")
+ .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "twilio", "httpclient"));
+ }
+
+ @DocumentExample
+ @Test
+ void raisesThePropertyTheVersionComesFrom() {
+ rewriteRun(
+ //language=xml
+ pomXml(
+ """
+