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.
- 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.
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
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
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 rulesinstructions
: 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
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 JavaProcessBuilder
.
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.
- Java 21 installed and Maven 3.9
First git clone this project and compile it.
./mvnw clean install
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 !
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.
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"
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
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) ----------------------------------------
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
!
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
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 ? |
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
, etcAPP_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
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
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