Skip to content

snowdrop/migration-tool

Repository files navigation

Migration Tool project

This project and proof of concept demonstrates how we could better manage end-to-end the migration process of a Java application, from by example Spring Boot to Quarkus, using new concepts able to perform the process with ordered instructions managed by different providers. A special attention has been paid improving how users can debug or test the transformation, particularly the matching condition and the resulting response.

Dictionary

  • Rule: A rule defines the information about what the tool will search within the code source of the project scanned like the instructions to be executed by a provider
  • Provider: A provider is a system able to execute the instructions defined part of a rule where the condition(s) matched. Different providers could be implemented such as: Openrewrite recipe, AI or user
  • Analysis report: The analysis report generated by a rule engine contains issues where each issue will tell to the tool or user what they should do, when it should be used and for which purpose.
  • Migration plan: A migration plan is an enhanced analysis report where the issue have been augmented with instructions that different providers could apply during the transformation step.

Migration Flow

Many tools, like the konveyor kantra client supports today the following flow. It generates an analysis report that users can use to review the issues reported, their criticality and what they should do to transform the code part of the message and/or links to websites.

---
title: Analysis and migration flow
---
flowchart LR
    A[Tool] --> |use rules|B[Scan Code]
    B --> |Generate|C[Analysis report]
    C -..-> |Execute|D[Transform]

    style A fill:#e1f5fe
    style C fill:#fff3e0
Loading

Note

The kantra client can be used to transform the code using openrewrite recipes.

While this flow, including also the transformation step, works pretty well, it suffers froms 2 limitations:

  • Lack of clear instructions to be executed during a transformation step
  • No order to play or execute the instructions.

While such limitations are not problematic for the users doing manually the transformation, that will become a real challenge when it is needed to apply recipes or delegate to AI the responsibility to propose solutions. AI will generate hallucinating results and this lack of predictability will discourage many users to rely on it. On the other site, as openrewrite during the execution of a recipe will compile the code, then the user will be faced to compilation errors, etc.

This is why it is important that we improve the existing flow to propose a more "controlled" flow as depicted hereafter:

---
title: Enhanced analysis and migration flow with instructions
---
flowchart LR
    A[Tool with controlled flow] --> |Use rules|B[Scan Code]
    B --> |Generate|C[Analysis report => Migration plan with tasks]
    C --> D{Do we have instructions?}
    D -->|Empty|E[No Action]
    D -->|if results|F{Select provider ?}
    F -->|User|G[Execute manual Tasks]
    F -->|AI|H[Prompt AI augmented messages]
    F -->|OpenRewrite|I[Apply Recipe]
    G --> J[Migrated Code]
    H --> J
    I --> J

    style A fill:#e1f5fe
    style C fill:#fff3e0
    style E fill:#f3e5f5
    style F fill:#fff8e1 
Loading

The rule represents, per se, the contract definition between what we would like to discover within the code source scanned: java, properties, xml, json, maven or gradle files and what a provider should do to properly transform the code.

As presented hereafter, we have introduced 2 new fields part of the Rule YAML file:

  • order: The order to apply the instructions against the flow which is composed of several rules
  • instructions: List of instructions/tasks to be executed by a provider
- ruleID: springboot-annotations-to-quarkus-00000
  category: mandatory
  description: Replace the Spring Boot Application Annotation with QuarkusMain
  labels:
    - konveyor.io/source=springboot
    - konveyor.io/target=quarkus

  message: "Replace the Spring Boot Application Annotation with QuarkusMain"
  
  when:
    java.referenced:
      location: ANNOTATION
      pattern: org.springframework.boot.autoconfigure.SpringBootApplication

  # Order to apply the instructions against the flow
  order: 2
  
  # New section added to help to transform properly the code !
  instructions:
    ai:
      - tasks:
        - "Remove the org.springframework.boot.autoconfigure.SpringBootApplication annotation from the main Spring Boot Application class"
    manual:
      - todo: "Remove the org.springframework.boot.autoconfigure.SpringBootApplication annotation from the main Spring Boot Application class"
    openrewrite:
      - name: Migrate Spring Boot to Quarkus
        preconditions:
          - name: org.openrewrite.java.dependencies.search.ModuleHasDependency
            groupIdPattern: org.springframework.boot
            artifactIdPattern: spring-boot
            version: '[3.5,)'
        recipeList:
          - dev.snowdrop.openrewrite.recipe.spring.ReplaceSpringBootApplicationAnnotationWithQuarkusMain
          - dev.snowdrop.openrewrite.recipe.spring.AddQuarkusRun
        gav:
          - dev.snowdrop:openrewrite-recipes:1.0.0-SNAPSHOT

The list of the AI's tasks will be executed one by one as user's chat message. When code is generated, the user will be able to accept or reject the proposition.

The openrewrite section contains the list of the recipes and/or preconditions to be executed using the maven openrewrite goal and gav are maven dependencies

Architecture of the PoC

The project uses the Spring TODO example as the java project to be analyzed using augmented rules.

The poc has been designed using the following technology:

  • Quarkus and Picocli to manage the CLI part and commands
  • konveyor jdt language server to scan the java files to search about using the rule when condition.
  • Openrewrite recipe to execute using the maven rewrite goal the transformation as defined part of the rule's instructions
  • The different applications: jdt-ls server, mvn command are executed as OS processes using Java ProcessBuilder.

Important

The rule engine of this PoC is pretty basic and only translate the YAML java.referenced value to the corresponding json request needed to execute the JSON-RPC call with the command io.konveyor.tackle.RuleEntry.

Requirements

  • Java 21 installed and Maven 3.9

Setup

First git clone this project and compile it.

./mvnw clean install

Konveyor jdt-ls

Download the konveyor language server using the image packaging it:

set VERSION latest

set ID $(podman create --name kantra-download quay.io/konveyor/kantra:$VERSION)
podman cp $ID:/jdtls ./jdt/konveyor-jdtls

Note

Copy the konveyor-jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jarjava-analyzer-bundle.core-1.0.0-SNAPSHOT.jar to the ./lib/ folder of this project to use it as dependency (to access the code) as it is not published on a maven repository server !

Hal client's commands

The client proposes 2 commands:

  • analyze: Scan the code source using the rules matching conditions and generate a JSON report augmented with the provider's instructions.
  • transform: Apply the transformation's instructions using as input the JSON report by using the chosen provider
Usage: hal [COMMAND]
Quarkus Hal client able to scan, analyze and migrate a java application using
instructions
Commands:
  analyze    Analyze a project for migration
  transform  Transform a java application
  help       Display help information about the specified command.

Note

The name of the tool hal - Heuristically Programmed Algorithmic Computer is coming from the AI that control the systems of the spacecraft of the movie`s Space Odyssey of Stanley Kubrick - https://en.wikipedia.org/wiki/HAL_9000.

Scan and analyze

To execute the command using the Quarkus Picocli CLI able to scan, analyze and generate the migration plan report (optional), execute this command

Usage: hal analyze [-v] [--jdt-ls-path=<jdtLsPath>]
                             [--jdt-workspace=<jdtWorkspace>] [-r=<rulesPath>]
                             <appPath>
Analyze a project for migration
      <appPath>             Path to the Java project to analyze
      --jdt-ls-path=<jdtLsPath>
                            Path to JDT-LS installation (default: from config)
      --jdt-workspace=<jdtWorkspace>
                            Path to JDT workspace directory (default: from
                              config)
  -r, --rules=<rulesPath>   Path to rules directory (default: from config)
  -v, --verbose             Enable verbose output
  
...  

mvn -pl migration-tool quarkus:dev -Dquarkus.args="analyze --jdt-ls-path /PATH/TO/java-analyzer-quarkus/jdt/konveyor-jdtls --jdt-workspace /PATH/TO/java-analyzer-quarkus/jdt -r /PATH/TO/java-analyzer-quarkus/rules ./applications/spring-boot-todo-app"

Tip

To avoid to pass all the parameters to the command, you can use the "defaults" application.properties and just pass the path of the application to be analyzed

mvn -pl migration-tool quarkus:dev -Dquarkus.args="analyze ../applications/spring-boot-todo-app"

Caution

If you experiment issues with the language server, you can check its log from this folder: jdt/.jdt_workspace/.metadata/.log !

During the execution of the command, you will be able to see within the terminal the log reporting the JSON responses when condition(s) matches like also a summary table.

2025-09-29 12:52:43,605 INFO  [dev.sno.ana.ser.LsSearchService] (ForkJoinPool.commonPool-worker-5) ==== CLIENT: --- Search Results found for rule: springboot-import-to-quarkus-00000.
2025-09-29 12:52:43,606 INFO  [dev.sno.ana.ser.LsSearchService] (ForkJoinPool.commonPool-worker-5) ==== CLIENT: --- JSON response: [
  {
    "name": "org.springframework.boot.autoconfigure.SpringBootApplication",
    "kind": 2.0,
    "location": {
      "uri": "file:///Users/cmoullia/code/application-modernisation/migration-tool-parent/applications/spring-boot-todo-app/src/main/java/com/todo/app/AppApplication.java",
      "range": {
        "start": {
          "line": 3.0,
          "character": 7.0
        },
        "end": {
          "line": 3.0,
          "character": 67.0
        }
      }
    },
    "containerName": ""
  }
]
...

=== Code Analysis Results ===
┌─────────────────────────────────────────────┬─────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                   Rule ID                   │Found│                                                       Information Details                                                        │
├─────────────────────────────────────────────┼─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│springboot-annotations-notfound-00000        │ No  │No symbols found                                                                                                                  │
├─────────────────────────────────────────────┼─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│springboot-annotations-to-quarkus-00000      │ Yes │Found SpringBootApplication at line 6, char: 1 - 22                                                                               │
│                                             │     │file:///Users/cmoullia/code/application-modernisation/migration-tool-parent/applications/spring-boot-todo-app/src/main/java/com/to│
│                                             │     │do/app/AppApplication.java                                                                                                        │
├─────────────────────────────────────────────┼─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│springboot-import-to-quarkus-00000           │ Yes │Found org.springframework.boot.autoconfigure.SpringBootApplication at line 4, char: 7 - 67                                        │
│                                             │     │file:///Users/cmoullia/code/application-modernisation/migration-tool-parent/applications/spring-boot-todo-app/src/main/java/com/to│
│                                             │     │do/app/AppApplication.java                                                                                                        │
└─────────────────────────────────────────────┴─────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘


If you want to populate an analysis report (aka migration plan) then pass the parameter -o json to the command. A json file having as name: analysing-report_yyyy-mm-dd_hh:mm.json will be generated within the project scanned !

mvn -pl migration-tool quarkus:dev -Dquarkus.args="analyze ../applications/spring-boot-todo-app -o json"

Transform your application

Now that we have a migration plan containing the of the instructions to be executed by a provider, we can perform the transformation using the command transform where we pass as parameter the provider to be used.

Usage: hal transform [-dv] [-p=<provider>] <appPath>
Transform a java application
      <appPath>   Path to the Java project to transform
  -d, --dry-run   Execute OpenRewrite in dry-run mode (preview changes without
                    applying them)
  -p, --provider=<provider>
                  Migration provider to use (ai, openrewrite, manual). Default:
                    from migration.provider property
  -v, --verbose   Enable verbose output

Note

The default provider is openwrite but you can use too: manual or ai

Openrewrite

To use openrewrite, execute the following command with or without the dryRun mode. If you use the --dry-run parameter, then openrewrite will generate rewrite.patch file(s) under the folder: target/rewrite of the analyzed project instead of changing the code directly !

mvn -pl migration-tool quarkus:dev -Dquarkus.args="transform ../applications/spring-boot-todo-app -p openrewrite --dry-run"

Log of the command executed

2025-09-29 13:03:20,735 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) ✅ Starting transformation for project at: /Users/cmoullia/code/application-modernisation/migration-tool-parent/applications/spring-boot-todo-app
2025-09-29 13:03:20,739 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) 📄 Loading migration tasks from: analysing-report_2025-09-26_14:05.json
2025-09-29 13:03:20,879 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) 📋 Found 3 migration tasks to process
2025-09-29 13:03:20,880 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) 🔄 Processing migration task: springboot-annotations-notfound-00000
2025-09-29 13:03:20,881 WARN  [dev.sno.com.TransformCommand] (Quarkus Main Thread)    ⚠️  No OpenRewrite instructions found for task, skipping
2025-09-29 13:03:20,882 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) 🔄 Processing migration task: springboot-annotations-to-quarkus-00000
2025-09-29 13:03:28,091 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread)    ✅ OpenRewrite execution completed successfully
2025-09-29 13:03:28,092 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) 🔄 Processing migration task: springboot-import-to-quarkus-00000
2025-09-29 13:03:28,093 WARN  [dev.sno.com.TransformCommand] (Quarkus Main Thread)    ⚠️  No OpenRewrite instructions found for task, skipping
2025-09-29 13:03:28,094 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) ----------------------------------------
2025-09-29 13:03:28,095 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) --- Elapsed time: 7359 ms ---
2025-09-29 13:03:28,096 INFO  [dev.sno.com.TransformCommand] (Quarkus Main Thread) ----------------------------------------

AI

Important

Until now, this Quarkus client only supports to use Anthropic and Claude Sonnet 4 model

To be able to perform the transformation of the code, using AI, it is needed to set part of the .env file some new properties:

QUARKUS_LANGCHAIN4J_ANTHROPIC_CHAT_MODEL_MODEL_NAME=premium
QUARKUS_LANGCHAIN4J_ANTHROPIC_BASE_URL=<THE_ANTHROPIC_API_SERVER>
QUARKUS_LANGCHAIN4J_ANTHROPIC_API_KEY=<YOUR_ANTHROPIC_API_KEY>
QUARKUS_LANGCHAIN4J_ANTHROPIC_TIMEOUT=60
MIGRATION_TOOL=/PATH/TO/migration-tool/target/quarkus-app/quarkus-run.jar

Source the .env file and don't forget to generate the analyze/migration plan report before to perform the transformation

mvn -pl migration-tool quarkus:dev -Dquarkus.args="analyze ../applications/demo-spring-boot-todo-app -o json"

As it is needed to interact with AI, then we cannot use the command mvn quarkus:dev but instead the uber jar file. Execute the following command within a Spring Boot project to be analyzed and migrated

pushd applications/demo-spring-boot-todo-app

export ANALYZER_APP_PATH=/PATH/TO/demo-spring-boot-todo-app
java -jar $MIGRATION_TOOL transform $ANALYZER_APP_PATH -p ai
popd

Check the console to see the tasks executed and AI's reponses:

2025-10-08 13:28:56,037 INFO  [dev.sno.com.TransformCommand] (main) 🔄 Processing migration task: springboot-replace-bom-quarkus-0000
2025-10-08 13:28:56,039 INFO  [dev.sno.tra.pro.imp.AiProvider] (main) Task: Add to the pom.xml file the Quarkus BOM dependency within the dependencyManagement section and the following dependencies: quarkus-arc, quarkus-core
2025-10-08 13:28:56,039 INFO  [dev.sno.tra.pro.imp.AiProvider] (main) Task: The version of quarkus to be used and to included within the pom.xml properties is 3.26.4.
2025-10-08 13:28:56,039 INFO  [dev.sno.tra.pro.imp.AiProvider] (main) Hello! I'm your AI migration assistant.
2025-10-08 13:28:58,981 INFO  [dev.sno.tra.pro.ai.FileSystemTool] (main) Reading file: pom.xml
2025-10-08 13:29:15,541 INFO  [dev.sno.tra.pro.imp.AiProvider] (main) ============= Claude response: Successfully added the Quarkus BOM dependency to the dependencyManagement section and included the quarkus-arc and quarkus-core dependencies. The Quarkus version property has also been added for version management.
2025-10-08 13:29:18,549 INFO  [dev.sno.tra.pro.ai.FileSystemTool] (main) Reading file: pom.xml
2025-10-08 13:29:33,810 INFO  [dev.sno.tra.pro.imp.AiProvider] (main) ============= Claude response: Successfully updated the Quarkus version to 3.26.4 in the properties section of the pom.xml file.
2025-10-08 13:29:33,811 INFO  [dev.sno.tra.TransformationService] (main) ✅ Task completed successfully:    ✅ ai execution completed successfully
...

If the AI responses are not accurate, then you will have to adapt the instructions as described part of the different Rules YAML files available here: cookbook/rules/ddd-springboot-****.yaml !

Tips

The analyze or transform commands can be executed using the quarkus uber jar file. Create in this case, an .env file, to configure properly the different properties needed:

# .env file content
ANALYZER_JDT_LS_PATH=jdt/konveyor-jdtls
ANALYZER_JDT_WORKSPACE_PATH=jdt
ANALYZER_RULES_PATH=cookbook/rules

Next source it and execute the following java commands:

java -jar migration-tool/target/quarkus-app/quarkus-run.jar analyze $(pwd)/applications/spring-boot-todo-app -o json
java -jar migration-tool/target/quarkus-app/quarkus-run.jar transform $(pwd)/applications/spring-boot-todo-app -p openrewrite --dry-run

If you want to test separately the openrewrite recipes, then use the openrewrite maven plugin command top of a spring boot project. Take care that your project is under git control as code will be transformed !

cd applications/spring-boot-todo-app
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
   -Drewrite.recipeArtifactCoordinates=dev.snowdrop:java-analyzer-quarkus:1.0.0-SNAPSHOT \
   -Dorg.openrewrite.quarkus.spring.ReplaceSpringBootApplicationAnnotationWithQuarkusMain

Instead of changing the code, you can use the dryrun goal to get a patch

cd applications/spring-boot-todo-app
mvn -U org.openrewrite.maven:rewrite-maven-plugin:dryRun \
   -Drewrite.recipeArtifactCoordinates=dev.snowdrop:openrewrite-recipes:1.0.0-SNAPSHOT \
   -Drewrite.activeRecipes=dev.snowdrop.openrewrite.recipe.spring.ReplaceSpringBootApplicationWithQuarkusMainAnnotation  

When done, open the diff patch generated: /PATH/TO/spring-boot-todo-app/target/rewrite/rewrite.patch

To execute several recipes aggregated in a yaml file placed at the root of the project, execute this command:

mvn -U org.openrewrite.maven:rewrite-maven-plugin:dryRun \
  -Drewrite.activeRecipes=dev.snowdrop.text.SearchText,dev.snowdrop.java.StandardJavaConventions,dev.snowdrop.java.spring.SearchSpringBootAnnotation \
  -Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-java:8.62.4,org.openrewrite.recipe:rewrite-java-dependencies:1.42.0,dev.snowdrop:openrewrite-recipes:1.0.0-SNAPSHOT \
  -Drewrite.exportDatatables=true \
  -Drewrite.configLocation=my-rewrite-1.yml
...
[WARNING] These recipes would make changes to applications/spring-boot-todo-app/my-rewrite.yml:
[WARNING]     dev.snowdrop.text.SearchText
[WARNING]         org.openrewrite.text.Find: {find=public class TaskController}
[WARNING] Patch file available:
[WARNING]     /Users/cmoullia/code/application-modernisation/migration-tool-parent/applications/spring-boot-todo-app/target/rewrite/rewrite.patch
[WARNING] Estimate time saved: 40m
[WARNING] Run 'mvn rewrite:run' to apply the recipes.  

Command using another YAML example

mvn -U org.openrewrite.maven:rewrite-maven-plugin:dryRun \
  -Drewrite.activeRecipes=dev.snowdrop.java.spring.SearchSpringBootAnnotation \
  -Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-java:8.62.4,org.openrewrite.recipe:rewrite-java-dependencies:1.42.0,dev.snowdrop:openrewrite-recipes:1.0.0-SNAPSHOT \
  -Drewrite.exportDatatables=true \
  -Drewrite.configLocation=my-rewrite-2.yml

TODO

Task Status Description Comment
MT-001 Check what spring-migrator-tool did to reuse some ideas to configure the instructions using Actions able to configure the recipe. The action's definition is used as input to apply the corresponding openrewrite's recipe.
MT-002 Investigate if the language server could be replaced using Openrewrite concepts such as: searchResult's marker, DataTable or Scanning recipe
MT-003 Discuss and define the level of granularity between what the rule targets to do and the steps/actions that the provider will support. Such a granularity can start with a 1 to 1 relation to a 1 to many but where the many is executed as a composite component or pipeline: https://docs.openrewrite.org/concepts-and-explanations/recipes#recipe-execution-pipeline, https://docs.openrewrite.org/concepts-and-explanations/recipes#scanning-recipes
MT-004 What about creating a migration plan like this one: https://docs.openrewrite.org/recipes/java/migrate/search/planjavamigration ?
MT-005 Do we have to integrate a workflow engine part of the solution to externalize the sequential approach of the openrewrite framework within a separate engine ?

Deprecated

Start using the JdtlsFactory Main application

Before to run the server and client, configure the following system properties or override the Quarkus propertiesapplication.properties:

  • JDT_WKS: Path of the folder containing the jdt-ls workspace, .metadata and log. Default: ./jdt/
  • JDT_LS_PATH: Path of the jdt language server folder. Default: ./jdt/konveyor-jdtls
  • LS_CMD: Language server command to be executed. Default: io.konveyor.tackle.ruleEntry, etc
  • APP_PATH: Path of the java project to analyze. Default: ./applications/spring-boot-todo-app
  • RULES_PATH: Path of the rules. Default: ./rules
mvn exec:java

Trick to path the eclipse osgi server

Here is the trick to do to add a bundle to the OSGI jdt-ls server. This step is optional as we will pass the bundle path as initialization parameter to the language server !

Edit the config.ini file corresponding to your architecture: mac, linux, mac_arm under the folder konveyor-jdtls/config_

Modify within the config.ini file the osgi.bundles property and include after the org.apache.commons.lang3... jar the BundleSymbolicName of: java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar

osgi.bundles=...org.apache.commons.lang3_3.14.0.jar@4,reference\:file\:java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar@2,...

Copy the java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar file from the path konveyor-jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/ to the plugins folder

Download jdt-ls

Alternatively, you can also download the Eclipse JDT Language Server:

wget https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/1.50.0/jdt-language-server-1.50.0-202509041425.tar.gz > jdt-language-server-1.50.0.tar.gz
mkdir jdt-ls
tar -vxf jdt-language-server-1.50.0.tar.gz -C jdt-ls

About

Project hosting the Quarkus scan, analyzer and migration tool to migrate spring to Quarkus

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published