Software Modernization with OpenRewrite
Automating Large-Scale Framework Migrations
Introduction
We are a small software consulting company based in Vietnam, currently focusing heavily on software modernization.
Most of our customers operate in the social security domain, where applications tend to be large, long-lived, and business-critical.
One of the key concepts we use to modernize software efficiently is OpenRewrite.
Today, I’ll show how we use it in practice.
If you like what you see: there are several people from Gepardec here today who know OpenRewrite inside out—feel free to talk to them afterward.
The Reality of Legacy Applications
Most real-world applications rely on a wide range of frameworks and libraries.
Especially in older systems, we often see:
Over time, these frameworks evolve. Examples:
-
Java EE → Jakarta EE
-
JUnit 4 → JUnit 5
-
major Quarkus upgrades
These are usually major upgrades, not minor version bumps.
They involve many manual code changes, which raises several challenges:
Why Manual Migration Doesn’t Scale
Jakarta EE as an Example
Jakarta EE 9 introduced a massive change:
But not everything changed.
For example:
On top of that, dependencies complicate things further.
You might update:
So you must migrate transitive dependencies as well, not just the ones you declare directly.
Organizational Problems
Manual migrations cause additional pain:
-
Developers continue working in parallel → huge merge conflicts
-
“Stop-the-world” migrations freeze development → business impact
-
Large organizations have many applications that all need the same changes
-
The same transformations are repeated again and again
Why OpenRewrite Exists
OpenRewrite originated at Netflix.
Netflix runs thousands of microservices.
They asked themselves:
“Why do we have to perform the same migration manually for every service?”
Their answer:
-
Treat migrations as code
-
Make them reproducible
-
Make them automatable
This led to the concept of recipes.
What Is a Recipe?
A recipe defines a set of code transformations.
There are two main types:
-
Composite recipes
-
Custom recipes
Why Not Just Use Search & Replace or AI?
Text Replacement (IDE / Regex)
-
Code meaning is not just text
-
Types and semantics matter
-
Regex cannot safely express semantic intent
Large Language Models (AI)
-
Not deterministic
-
Hard to trace why something changed
-
Risky for regulated environments
-
Often not allowed due to data residency constraints
That said: LLMs and OpenRewrite can complement each other, for example by recommending recipes.
What OpenRewrite Supports
OpenRewrite works across many formats:
-
Java
-
XML
-
YAML
-
JSON
-
Properties
-
Groovy
-
Maven & Gradle metadata
Example: Jakarta EE Migration Recipe
A standard Jakarta EE 11 recipe:
-
Updates Java imports (javax → jakarta)
-
Updates Maven dependencies
-
Updates XML configuration files (e.g. beans.xml)
Execution is straightforward via the Rewrite Maven Plugin.
Dry Run vs Run
We usually prefer dry runs, because IDEs like IntelliJ can review and apply patches safely.
Extending Recipes with Composite YAML
Sometimes an official recipe doesn’t cover everything.
Example:
Solution:
Now one execution handles everything consistently.
Beyond Code Changes: Impact Analysis
OpenRewrite is also useful before a migration.
Data Table Recipes
These generate CSV files describing your codebase:
-
language composition
-
files per package
-
lines of code
-
dependency usage
This helps answer questions like:
The Transformer Approach to Migration
Instead of stopping development:
At the end:
This avoids merge hell and keeps progress measurable.
Preconditions for Successful Automation
Before using OpenRewrite effectively, you need:
-
Explicit dependencies (no hidden transitive reliance)
-
Good test coverage
-
Clean structure (remove dead code, cycles, unused modules)
Automation magnifies quality — good or bad.
Migration in Iterative Phases
We slice migrations into cycles:
-
Compile
-
Unit tests
-
Deployment
-
Integration tests
Each failure:
Repeat until green.
Writing Custom Recipes (Advanced)
OpenRewrite builds a Lossless Semantic Tree (LST):
-
Preserves formatting & comments
-
Includes full type information
-
Enables precise transformations
Recipes use the Visitor Pattern:
We demonstrated:
-
writing a custom Java recipe
-
testing it with before/after assertions
-
ensuring idempotency and type correctness
Recipe Quality Criteria
A good recipe must be:
Sharing Recipes
Two common patterns:
-
Internal toolbox
-
Library-driven migrations
Gepardec Auto Update Service
At Gepardec, we combine:
-
Renovate Bot (detects new versions)
-
OpenRewrite (performs migrations)
-
CI pipelines
-
Dev containers for manual fixes
Result:
-
Automated pull requests
-
Verified by tests
-
Minimal manual effort
-
Continuous modernization
Closing & Q&A
OpenRewrite is:
Slides, examples, and resources are available via the QR code provided.
Thank you for your attention.