<
Media
>
Article

OpenRewrite, pour les migrer tous… et dans le refactoring les lier

7
min
15
/
07
/
2025

Migrer de Java 8 à Java 21 vous fait peur ? Migrer de Spring Boot 2 à Spring Boot 3 vous angoisse ? Migrer de JUnit 4 à JUnit 5 coûte trop cher ?

Et si je vous disais qu’un outil pouvait automatiser toutes ces migrations — de Java EE vers Jakarta EE, des anciennes API Date/Calendar vers java.time, et bien plus encore — sans prise de tête ni perte de temps ?

Vous en rêviez, OpenRewrite le fait ! 🎉

OpenRewrite ?

OpenRewrite est un écosystème Open-Source de re-factorisation automatisée permettant aux développeurs d'éliminer efficacement la dette technique au sein de leurs référentiels.

Sa promesse ? Réduire le temps des upgrades techniques de plusieurs jours à quelques minutes. Dans l'esprit, ça interpelle, non ?

Comment ça marche ?

OpenRewrite fonctionne en modifiant ce qu'ils appellent des arbres sémantiques sans perte (LST) représentant votre code source, puis en modifiant ces arbres et en les réintégrant dans votre code.  

Pour vulgariser, une LST est une structure arborescente qui représente la façon dont le code est compris et analysé par un interpréteur ou un compilateur.  

Le concept vous parait compliqué ? Voyez le LST comme un AST (Abstract Syntax Tree) mais enrichi : avec les attributs de type et le format (espaces avant/après etc) qui permet un rendu, après transformation de votre code, identique à l'origine. Les modifications sur les LST sont réalisées dans un ou plusieurs visiteurs (Pattern Visitor), eux-mêmes regroupés en recettes (recipes).

Voici une illustration assez simple que propose OpenRewrite sur son site :

Principalement conçu pour Java, OpenRewrite est capable de traiter d'autres langages comme Kotlin, Groovy, XML, YAML, JSON, SQL... Mais pour la suite, je me focaliserai sur Java (avec un soupçon de YAML et de XML).

OK, c'est bien beau tout ça, mais comment je l'intègre dans mon projet et comment je dois m'en servir ?

Intégration par l'exemple

Pour bien comprendre ce que vous apporte OpenRewrite, nous allons l'intégrer dans un projet Java (disponible ici) basé sur les technologies suivantes, que vous retrouverez dans la branche main :

  • Java 8
  • Spring Boot 2
  • Junit 4
  • Mockito 3
  • Maven

Notre but ici est de migrer vers :

  • Java 21
  • Spring Boot 3.3
  • Junit 5
  • Mockito 5

Migrer avec OpenRewrite

OpenRewrite propose de réaliser ces migrations soit via un CLI (rewrite CLI), soit via les plugins Maven (rewrite-maven-plugin) ou Gradle (org.openrewrite.rewrite). Il existe également une solution payante appelée "Moderne" que je n'aborderai pas. J'utiliserai par la suite le plugin Maven.

Pour ce faire, rien de plus simple, ajoutons ce dernier à notre fichier <span class="css-span">pom.xml</span> :

    <plugin>
        <groupId>org.openrewrite.maven</groupId>
        <artifactId>rewrite-maven-plugin</artifactId>
    &lt;/plugin>

Rappelez-vous que nous souhaitons migrer vers Java 21, Spring Boot 3.3, JUnit 5 et Mockito 5. Pour cela, il suffit de récupérer sur le site d'OpenRewrite la configuration des recettes équivalentes :

--

Note

En cherchant une recette spécifique sur le site d'OpenRewrite, vous constaterez plusieurs mentions dans le chapitre "Recipe Source" :

­• "This recipe is available under the Moderne Source Available License." (en français, "Cette recette est disponible sous la licence Moderne Source Available.")

• "This recipe is only available to users of Moderne." (en français, "Cette recette est uniquement accessible aux utilisateurs de Moderne.")

Et là, ça change tout.  

En effet, la première vous autorise à utiliser la recette sans la modifier, sans la redistribuer et sans l'intègrer dans un produit ou une offre commerciale.

Tandis que la deuxième ne vous permet ni ne vous autorise à l'utiliser.

--

Comment intégrer les recettes dans la configuration du plugin ? Rien de plus simple, une fois votre liste établie, il suffit d'ajouter ce bloc dans votre déclaration du plugin Maven :

<configuration>
    <activeRecipes>
        <recipe>org.openrewrite.java.migrate.UpgradeToJava21</recipe>
        <recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3</recipe>
        <recipe>org.openrewrite.java.testing.mockito.Mockito1to5Migration</recipe>
    &lt;/activeRecipes>
&lt;/configuration>

Certaines recettes ont également besoin de dépendances Maven supplémentaires. Vous trouverez en détail lesquelles dans chacune des recettes depuis le site OpenRewrite. Ces dépendances sont à ajouter dans votre déclaration du plugin.

La recette de migration Java 21 a besoin de la dépendance :

<dependency>
    <groupId>org.openrewrite.recipe</groupId>
    <artifactId>rewrite-migrate-java</artifactId>
&lt;/dependency>

La recette Spring Boot a besoin de la dépendance :

<dependency>
    <groupId>org.openrewrite.recipe</groupId>
    <artifactId>rewrite-spring</artifactId>
&lt;/dependency>

Enfin, la recette Mockito 5 a besoin de celle-ci :

<dependency>
    <groupId>org.openrewrite.recipe</groupId>
    <artifactId>rewrite-testing-frameworks</artifactId>
&lt;/dependency>

Une fois cette configuration - honnêtement très rapide - réalisée, nous n'avons plus qu'à exécuter le plugin OpenRewrite via la commande <span class="css-span">mvn rewrite:run</span>.

Analysons ensuite les logs du build pour la partie migration vers Java 21 :

[INFO] Using active recipe(s) [org.openrewrite.java.migrate.UpgradeToJava21, org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3, org.openrewrite.java.testing.mockito.Mockito1to5Migration]
...
[WARNING] Changes have been made to pom.xml by:
[WARNING]     org.openrewrite.java.migrate.UpgradeToJava21
[WARNING]         org.openrewrite.java.migrate.UpgradeToJava17
[WARNING]             org.openrewrite.java.migrate.Java8toJava11
[WARNING]                 org.openrewrite.java.migrate.UpgradeBuildToJava11
[WARNING]                     org.openrewrite.java.migrate.UpgradeJavaVersion: {version=11}
[WARNING]                         org.openrewrite.maven.UpdateMavenProjectPropertyJavaVersion: {version=11}
[WARNING]             org.openrewrite.java.migrate.UpgradeBuildToJava17
[WARNING]                 org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING]                     org.openrewrite.maven.UpdateMavenProjectPropertyJavaVersion: {version=17}
[WARNING]             org.openrewrite.java.migrate.UpgradePluginsForJava17
[WARNING]                 org.openrewrite.maven.UpgradePluginVersion: {groupId=org.apache.maven.plugins, artifactId=maven-compiler-plugin, newVersion=3.x}
[WARNING]             org.openrewrite.java.migrate.AddLombokMapstructBinding
[WARNING]                 org.openrewrite.maven.AddDependency: {groupId=org.projectlombok, artifactId=lombok-mapstruct-binding, version=0.2.0, acceptTransitive=false}
[WARNING]         org.openrewrite.java.migrate.UpgradeBuildToJava21
[WARNING]             org.openrewrite.java.migrate.UpgradeJavaVersion: {version=21}
[WARNING]                 org.openrewrite.maven.UpdateMavenProjectPropertyJavaVersion: {version=21}
[WARNING] Changes have been made to src\main\java\fr\example\sample_open_rewrite\classroom\ClassroomController.java by:
[WARNING]     org.openrewrite.java.migrate.UpgradeToJava21
[WARNING]         org.openrewrite.java.migrate.UpgradeToJava17
[WARNING]             org.openrewrite.java.migrate.Java8toJava11
[WARNING]                 org.openrewrite.java.migrate.UpgradeBuildToJava11
[WARNING]                     org.openrewrite.java.migrate.UpgradeJavaVersion: {version=11}
[WARNING]             org.openrewrite.java.migrate.UpgradeBuildToJava17
[WARNING]                 org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING]         org.openrewrite.java.migrate.UpgradeBuildToJava21
[WARNING]             org.openrewrite.java.migrate.UpgradeJavaVersion: {version=21}
...

On remarque tout de suite que OpenRewrite procède par étape, migration de Java 8 vers Java 11, puis vers Java 17 et enfin vers Java 21, dans un premier temps dans le fichier <span class="css-span">pom.xml</span>, puis dans chaque classe.

En analysant le log dans son intégralité, on remarque qu'il fait la même chose pour chaque recette.

Enfin, Le log se termine par :

[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 10m

OpenRewrite a estimé que les changements qu'il a réalisés ont permis d'économiser 10 minutes de développement. Pour l'avoir fait manuellement, il m'a clairement fallu plus de temps. Mais cette application est très petite, donc on peut le croire.

Il nous informe aussi que les changements sont disponibles directement dans notre code.  

Si nous jetons un œil aux classes Java, on remarque qu'il a modifié les imports <span class="css-span">import javax.xxx</span> en <span class="css-span">import jakarta.xxx</span>. On en déduit facilement que cette modification est dûe à la migration de Java 8 à 21.

OpenRewrite via la recette de migration Spring Boot a également modifié les propriétés dans les fichiers <span class="css-span">application.yml</span>, en modifiant la propriété <span class="css-span">management.metrics.export.prometheus.enabled: false</span> en <span class="css-span">management.prometheus.metrics.export.enabled: false</span>.

Si on jette un œil au <span class="css-span">pom.xml</span>, on remarque quelques changements majeurs :

  • la version du starter <span class="css-span">spring-boot-starter-parent</span> est passée de 2.7.X à 3.3.X
  • les propriétés <span class="css-span">maven.compiler.source</span>, <span class="css-span">maven.compiler.target</span> et <span class="css-span">java.version</span> sont passées de 8 à 21
  • la dépendance <span class="css-span">springdoc-openapi-ui</span> a été migrée en <span class="css-span">springdoc-openapi-starter-webmvc-ui</span>
  • la dépendance <span class="css-span">lombok-mapstruct-binding</span> a été ajoutée
  • la dépendance <span class="css-span">junit-vintage-engine</span> a été supprimée
  • la version de la dépendance <span class="css-span">mockito-core</span> a été retirée, Mockito 5 étant tiré transitivement par Spring Boot Test 3 (elle aurait même pu être retirée...)

Plus qu'à builder le projet et surprise, ça compile, ça builde, les tests passent, la migration est terminée 🥳

💡Tips
Retrouvez le commit de la configuration OpenRewrite

Ajouter la prise en compte des Threads Virtuels

Java 21 et Spring Boot 3 gèrent les Threads Virtuels. Donc autant les activer.  

Est-ce qu'OpenRewrite le permet ? Oui !! et non...  

Disons qu'il n'y a pas de recette toute faite pour ajouter cette propriété, mais ça reste très simple à faire !

Créeons notre propre recette en nous basant sur une déjà existante ! On commence par créer le fichier <span class="css-span">.rewrite/add-virtual-threads-spring-property.yml</span>, à la racine de notre projet, contenant :

# type: Informe OpenRewrite que ce fichier contient une recette, conforme au schéma v1beta publié par OpenRewrite.
type: specs.openrewrite.org/v1beta/recipe

# name: Identifiant interne de notre recette (en PascalCase)
name: AddSpringVirtualThreadsProperty 

# displayName : Nom visible dans Moderne ou dans le plugin Intellij "Rewrite Intellij Plugin"
displayName: Add `spring.threads.virtual.enabled=true` property to the application.yml/properties file

# Liste des recettes à appliquer
recipeList:

    # Recette incluse dans OpenRewrite, fournie par le module rewrite-spring, elle sert à ajouter une propriété Spring
  - org.openrewrite.java.spring.AddSpringProperty: 

        # property : Propriété Spring à ajouter
        property: spring.threads.virtual.enabled 

        # value : Valeur que prendra la propriété Spring ajoutée
        value: "true" 

Côté <span class="css-span">pom.xml</span> et configuration du plugin, il suffit d'ajouter notre recette (dans la balise <span class="css-span">< activeRecipes></span>)

<recipe>AddSpringVirtualThreadsProperty&lt;/recipe>

et l'emplacement de celle-ci (dans la balise <span class="css-span">< configuration></span>) :

<configLocation>.rewrite/add-virtual-threads-spring-property.yml&lt;/configLocation>
💡Tips
Retrouvez le commit associé

Plus qu'à relancer la commande <span class="css-span">mvn rewrite:run</span> et à jeter un œil aux logs et aux modifications :

[WARNING] Changes have been made to src\main\resources\application.yml by:
[WARNING]     AddSpringVirtualThreadsProperty
[WARNING]         org.openrewrite.java.spring.AddSpringProperty: {property=spring.threads.virtual.enabled, value=true}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m

Côté <span class="css-span">application.yml</span>, la propriété est bien apparue 🥳

Bilan, en ajoutant simplement le plugin OpenRewrite à notre <span class="css-span">pom.xml</span>, en lui configurant 3 recettes existantes (faciles à trouver dans la documentation), en lui ajoutant les dépendances requises, et en créant notre propre recette pour ajouter une propriété Spring, on a migré notre application legacy vers une application Spring Boot 3, Java 21, avec les dernières versions des frameworks de tests, on a migré les propriétés Spring Boot et on a activé les Threads Virtuels ! Tout ça en 10 minutes ? 15 maximum ? Quel bonheur 😍

💡Tips
Retrouvez les résultats de la migration

Dans les profondeurs du code fut forgé… un plugin OpenRewrite 🔥

Je vous entends déjà dire "c'est tout ce que sait faire OpenRewrite ?"  

Evidemment que non 😁 OpenRewrite permet d'écrire ses propres recettes, ou devrais-je plutôt dire qu'il permet de coder ses propres recettes !  

Voyons comment développer une recette qui ajouterait l'annotation <span class="css-span">@Tag("unit")</span> sur nos tests unitaires et l'annotation <span class="css-span">@Tag("integration")</span> sur nos tests d'intégration. Tags qui nous permettront de configurer plus simplement les plugins Maven Surefire et FailSafe, respectivement comme suit :

<configuration>
  <groups>unit</groups>
&lt;/configuration>

et

<configuration>
  <groups>integration</groups>
&lt;/configuration>

Première chose à faire, écrire notre <span class="css-span">pom.xml</span>. On va avoir besoin des dépendances <span class="css-span">rewrite-java-21</span>, <span class="css-span">rewrite-test</span>, <span class="css-span">rewrite-testing-frameworks</span> et de <span class="css-span">junit-bom</span>.

À première vue, coder une recette est assez simple, on doit étendre la classe <span class="css-span">Recipe</span> et implémenter quelques méthodes : <span class="css-span">getDisplayName()</span>, <span class="css-span">getDescription()</span> et <span class="css-span">getVisitor()</span>.  

Vous l'aurez compris, les deux premières sont simples.  

La dernière, en revanche, doit contenir notre algorithme. Celle-ci va devoir retourner un <span class="css-span">TreeVisitor<?, ExecutionContext></span> — ici, une instance d'une classe anonyme qui hérite de <span class="css-span">JavaIsoVisitor<></span> — et il faut choisir les bonnes méthodes à implémenter en fonction de notre use-case. Nous savons déjà que nous souhaitons ajouter une annotation à chacune de nos classes de tests et y ajouter l'import qui convient. Nous allons donc implémenter les méthodes <span class="css-span">visitCompilationUnit()</span> et <span class="css-span">visitClassDeclaration</span>.

La méthode <span class="css-span">visitCompilationUnit()</span> est appelée au tout début de la visite d'un fichier Java. C'est au sein de celle-ci que nous pouvons analyser et/ou modifier les imports, les annotations de classes, les packages...

La méthode <span class="css-span">visitClassDeclaration</span> est appelée à chaque fois qu'une classe est rencontrée dans le code. Elle permet de vérifier si une classe est un test, ajouter/supprimer une annotation de classe, renommer la classe, inspecter les méthodes et les champs...

En d’autres termes, la première vous fait entrer dans Khazad-dûm, le vaste royaume des Nains, et la seconde vous guide à travers ses grandes salles, où reposent les secrets enfouis du code : les classes.

Mais pour commencer notre quête, il nous faut d’abord identifier les classes qui nous intéressent : les classes de tests. Facile, en général, elles se trouvent dans <span class="css-span">src/test</span> et suivent le pattern <span class="css-span">* \ * Test.java *</span>. On s'assure également qu'elles contiennent au moins une méthode annotée <span class="css-span">@Test</span>.

Ensuite, on va différencier les classes de tests d'intégration des classes de tests unitaires. La méthode que j'ai choisie repose sur la recherche d'une annotation de la classe elle-même : si celle-ci est annotée par une des annotations suivantes, on en conclut que c'est une classe de tests d'intégration : <span class="css-span">@SpringBootTest</span>, <span class="css-span">@DataJpaTest</span>, <span class="css-span">@WebMvcTest</span>, <span class="css-span">@WebFluxTest</span>, <span class="css-span">@JdbcTest</span>, <span class="css-span">@DataMongoTest</span>, <span class="css-span">@DataRedisTest</span>, <span class="css-span">@DataCassandraTest</span>, <span class="css-span">@RestClientTest</span>. Cette liste n'est sans doute pas exhaustive, mais dans notre cas, on prendra comme hypothèse que c'est suffisant.

On vérifie également que cette classe ne contient pas déjà l'annotation <span class="css-span">@Tag</span> et l'import associé <span class="css-span">org.junit.jupiter.api.Tag</span>. Ensuite, on ajoute l'annotation <span class="css-span">@Tag("unit")</span> ou <span class="css-span">@Tag("integration")</span> et l'import.

Notre recette est terminée, mais je vous conseille de réaliser une dernière chose importante, ajouter des tests unitaires pour valider celle-ci.

On compile, on génère le jar et en route pour l'intégration dans notre projet !

Là, encore rien de plus simple, ajoutons la recette à la liste déjà présente

<activeRecipes>
    <recipe>fr.example.recipe.AddTestTagRecipe</recipe>
&lt;/activeRecipes>

et la dépendance vers la recette

<dependency>
    <groupId>fr.example</groupId>
    <artifactId>add-test-tag-open-rewrite-recipe</artifactId>
    <version>1.0.0</version>
&lt;/dependency>

Une chose à savoir cependant. Notre recette utilise des éléments de JUnit5, il faut donc que notre déclaration de plugin OpenRewrite contienne la dépendance vers JUnit5.

Relançons la commande <span class="css-span">mvn rewrite:run</span> et... notre application est upgradée, et tous nos tests sont annotés 😍

💡Tips
Retrouvez le code de cette recette
Retrouvez l'intégration de cette recette dans notre application

Les résultats

Les comparaisons ci-dessous montrent le travail réalisé par OpenRewrite en appliquant l'ensemble des recettes que nous avons vues.

• Exemple de comparaison du code source :

• Exemples de comparaison des imports et annotations d'une classe de tests unitaires :

• Exemples de comparaison de code d'une classe de tests unitaires :

• Exemple de comparaison des imports et annotations d'une classe de tests d'intégration :

• Exemple de comparaison de code d'une classe de tests d'intégration :

• Exemples de comparaison du pom.xml :

• Comparaison du fichier de configuration :

💡Tips
Retrouvez le résultat de la migration

Allez plus loin dans la Terre du Milieu 🧙‍♂️

L’aventure ne s’arrête pas là ! Pour explorer d’autres contrées et perfectionner votre maîtrise d’OpenRewrite :

  • parcourez la documentation officielle, vous y découvrirez d'autres recettes et cas d'usage,
  • testez l'intégration avec Gradle ou le CLI pour des projets non Maven ou Gradle,
  • expérimentez la création de vos propres recettes...

OpenRewrite, un outil pour les migrer tous 💍

Vous l'aurez compris, OpenRewrite ne demande qu'une courte montée en compétence, et très vite, vous serez capable d'upgrader vos applications en un rien de temps.  

Netflix l'a utilisé pour migrer ses quelques 3000 applications Spring Boot, pourquoi pas vous ?

Certes, les recettes les plus récentes nécessitent une licence, mais comme vous l'avez vu, on peut déjà faire énormément - rapidement et simplement - avec celles fournies gratuitement.

Mon conseil ? Essayez-le ! Vous ne verrez plus vos migrations techniques du même œil 😉

📜 Sources :

Site Officiel
AST
Pattern Visitor
Communauté Open Rewrite sur GitHub

🔥 Notre code :

Application Java
Plugin OpenRewrite

No items found.
ça t’a plu ?
Partage ce contenu
Cédric

Cédric est un Architecte Logiciel, mais plutôt Backend et plutôt Java. Il aime toutes les technologies, surtout l'éco-système Spring, puisque Pivotal arrive toujours à sortir de nouvelles librairies et continue de surprendre la communauté. Mais bon, il en aime plein d'autres : l'architecture Monolithique Modulaire et le Domain Driven Design, Sonarqube et le Clean as You Code, les bases de données relationnelles... En dehors de son métier-passion, il déteste deux choses : le mois de janvier sans neige, et s'ennuyer. Les 2 sont d'ailleurs peut-être liées...

Retrouvez le sur Linkedin