Modernization
Enterprise AI
Java modernization with OpenRewrite and Droids
November 14, 2026 - 6 minute read
Modernization
Enterprise AI
November 14, 2026 - 6 minute read
Java modernization with OpenRewrite pairs a deterministic refactoring engine with an agent that handles everything the engine cannot. OpenRewrite recipes apply the mechanical changes of a Java 21, Spring Boot 3, or javax to jakarta migration the same way every time. Droids, the coding agents in Factory, repair the compile and test failures that remain, migrate internal APIs that no public recipe knows about, and turn repeated fixes into new recipes.
Factory has no built-in OpenRewrite integration and no special Java mode. Droids run the same Maven and Gradle plugins an engineer runs, and Factory's hooks, skills, Droid Exec, and Missions keep those runs and their verification inside every step of the work.
OpenRewrite parses source code into a lossless semantic tree that keeps formatting, comments, and type attribution. A recipe is a program that visits that tree and edits it. Composite recipes chain many smaller ones, which is how a single recipe name can describe an entire upgrade.
The UpgradeToJava21 recipe shows the scale. It first runs the Java 17 migration, then updates the build target, upgrades plugins to Java 21 compatible versions, rewrites new URL(String) to URI.create(String).toURL(), adopts SequencedCollection, and converts eligible if chains and switch statements to switch expressions and pattern matching. UpgradeToJava25 follows the same pattern for the newer LTS release.
Two other entry points cover most enterprise Java estates. org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5 from the rewrite-spring module moves a Spring Boot 2.x application to 3.5. org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta renames the javax.servlet, javax.persistence, javax.validation, and related packages to their jakarta equivalents and updates XML namespaces in files such as persistence.xml.
Check licensing and artifact access before you plan a rollout. As of November 2026, the core framework, building-block recipes such as ChangeType, and the build plugins are Apache licensed, while modules such as rewrite-migrate-java and rewrite-spring use the Moderne Source Available License. Releases now resolve from the Code Genome Project repository, which requires a download token. The OpenRewrite licensing page explains which modules need a Moderne subscription.
Error Prone covers a narrower slice of the same ground at compile time. Its patching mode applies suggested fixes for named checks with -XepPatchChecks and -XepPatchLocation:IN_PLACE, which is useful for clearing warning-level findings before or after a larger recipe run.
The Maven plugin exposes run to apply changes and dryRun to write a patch under target/rewrite without touching sources. The rewrite-maven-plugin reference lists every goal and property. These commands follow the documented form, with Code Genome Project credentials already configured in settings.xml.
Gradle projects get the same behavior from rewriteRun and rewriteDryRun, with failOnDryRunResults set in the rewrite block.
Run one composite recipe per commit and keep the commit free of hand edits. That discipline pays off during long migrations. When the base branch moves, the recipe commit can be dropped and regenerated by running the recipe again on the new base, instead of resolving merge conflicts by hand in hundreds of files. The same property lets a team rerun a recipe after upgrading the recipe module and see exactly what the new version adds.
Factory is a platform for running a software factory, where Droids take work from a signal to a reviewed pull request. For a Java migration, that work follows a fixed loop. The recipe runs first, the build decides what is left, the Droid handles only that residue, and deterministic checks run again before a person reviews.
Exact commands in AGENTS.md. Factory's AGENTS.md guidance puts the Maven wrapper invocation, the module list, and the test command in a file committed with the code. Droid reads it before editing, so it runs ./mvnw verify with the project's profiles instead of guessing.
The playbook as a skill. A skill packages the migration procedure in .factory/skills/<name>/SKILL.md, so every session follows the same order and commit discipline.
---
name: java-openrewrite-migration
description: Run an OpenRewrite migration recipe on a Maven project, repair what it leaves, and verify. Use for Java, Spring Boot, or Jakarta EE upgrades.
---
1. Start from a clean tree and a passing `./mvnw -q verify`.
2. Run the recipe named in the task with `rewrite:run`. Commit only its output.
3. Run `./mvnw -q verify`. Fix failures without reverting recipe edits, then commit them separately.
4. Run `rewrite:dryRun -DfailOnDryRunResults=true` and require zero remaining changes.
5. List every manual decision in the pull request description.A hook that refuses to stop early. Factory hooks run shell commands at lifecycle events from .factory/hooks.json. A Stop hook that exits with code 2 sends its stderr back to Droid instead of letting the session end. Pointing one at a script that runs the build and the failing dry run means a Droid cannot report success while a recipe still has work to do.
{
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$FACTORY_PROJECT_DIR\"/.factory/hooks/rewrite-verify.sh",
"timeout": 900
}
]
}
]
}A recipe stops where its authors stopped. After UpgradeSpringBoot_3_5, a typical service still has internal shared libraries compiled against javax types, string-based class names in configuration, tests that assert on behavior that changed with the framework upgrade, and places where two replacement libraries are both reasonable.
Those are the Droid's job. It reads the compiler output and failing tests, fixes each one at its source, and records the decisions a reviewer should check. It does not revert recipe edits to make the build pass.
Thin test coverage is the main risk in this step, because a green build only proves what the tests exercise. Before the recipe runs, the dry-run patch already lists every file the recipe will touch. Comparing that list with a JaCoCo coverage report shows which changed classes have no tests, and the Droid can write characterization tests for those classes against the old code first. Those tests then run unchanged against the migrated code, so a behavior change shows up as a failing assertion rather than a production incident.
When the same manual fix appears in several places, the better move is a new recipe. A declarative recipe in rewrite.yml composes existing building blocks.
---
type: specs.openrewrite.org/v1beta/recipe
name: com.yourorg.MigrateLegacyHttpClient
displayName: Migrate to the platform HTTP client
description: Replace the deprecated internal client type and rename its execute method.
recipeList:
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: com.yourorg.legacy.LegacyHttpClient
newFullyQualifiedTypeName: com.yourorg.http.PlatformHttpClient
- org.openrewrite.java.ChangeMethodName:
methodPattern: com.yourorg.http.PlatformHttpClient execute(..)
newMethodName: sendThe Droid writes tests for that recipe with RewriteTest, pointing the spec at the YAML with recipeFromResources and asserting before and after source for each case, including a case that must stay unchanged. The OpenRewrite recipe testing guide documents the harness. Logic that YAML cannot express becomes an imperative Java recipe tested the same way. Once the recipe ships in a shared JAR under META-INF/rewrite, every other repository gets the fix deterministically.
One repository is a session. A portfolio of services needs fan-out.
Droid Exec runs Droid headless in CI or a script. The documented lint autofix recipe shows the shape: a deterministic tool lists the targets, one droid exec run fixes each target in isolation, and the tool re-verifies each one. For Java, the list comes from modules whose dry run reports changes or whose build fails after the recipe commit. Each module gets its own run with --auto medium, which permits local builds and tests but not git push.
For a migration across many services with an ordering constraint, such as shared libraries before consumers or Java 17 before Java 21, Factory Missions plan the work as features and milestones and validate progress as they go. The Missions documentation lists brownfield migrations as a primary use case.
Review stays deterministic where it can be. Each pull request carries one commit of recipe output that a reviewer can trust by recipe name and re-run, and one or more agent commits that deserve line-by-line reading. A clean failOnDryRunResults run plus a green build on the target JDK is the evidence that the migration is complete.
OpenRewrite parses Java sources and build files into lossless semantic trees with type information, then applies recipes that edit those trees. The same recipe on the same code produces the same diff, so the mechanical part of a Java 21, Spring Boot 3, or Jakarta EE migration becomes repeatable and reviewable as a single change.
No. Factory runs OpenRewrite through the same Maven and Gradle plugins an engineer would use. Hooks, skills, AGENTS.md, Droid Exec, and Missions keep the recipe run and its verification inside every Droid session, and Droids write new recipes when no existing one covers a change.
It fixes the compile errors and test failures the recipe leaves, migrates internal APIs that no public recipe knows about, makes judgment calls such as choosing a replacement library, and writes new declarative or Java recipes with RewriteTest before and after tests so the fix becomes deterministic.
Compile and run the full test suite, then run rewrite:dryRun with failOnDryRunResults set to true so the build fails if any active recipe would still change the code. Keep recipe output and agent edits in separate commits so reviewers know which diff came from the deterministic tool.
The core framework, the building-block recipes, and the Maven and Gradle plugins are Apache licensed. Several migration modules, including rewrite-migrate-java and rewrite-spring, use the Moderne Source Available License, and releases are distributed through the authenticated Code Genome Project repository. Check the OpenRewrite licensing page and your account access before planning a rollout.
Plan legacy modernization with private AI around behavior preservation, internal dependencies, and reviewable milestones. Keep code and validation inside the approved boundary.
PHP modernization works best when Rector performs the mechanical upgrade, PHPStan and the test suite gate every step, and Factory Droids handle the residue and write new Rector rules. A practical setup with hooks, skills, and Droid Exec.
JavaScript and TypeScript modernization pairs framework codemods, jscodeshift, ESLint, and tsc with Droids. Tools make the mechanical changes, Droids fix the residue and write tested codemods, and a dry-run rerun proves nothing is left.
Start building