Posts Tagged ‘Rewrite’
[SpringIO2026] Hybrid Modernization: Combining OpenRewrite’s Precision with LLM Intelligence for Spring
Lecturer
Raquel Pau is a technical product manager at Broadcom (formerly VMware Tanzu). She brings extensive experience in Java developer tools, continuous-integration and continuous-delivery platforms, and internal developer platforms. Previously she worked as an engineering manager at Moderne, the company behind OpenRewrite, and held product-management roles at CloudBees focused on developer productivity. She has spoken at multiple Spring I/O editions as well as Devoxx, JavaConf and JavaZone. Her background combines deep technical knowledge of code-transformation tooling with product thinking about how large organizations can keep their application portfolios modern and consistent.
Abstract
Code modernization is not a single problem. Upgrading a Spring Boot application within the same major version, migrating from JAX-RS to Spring MVC, and rewriting a COBOL batch job into Spring Batch demand fundamentally different strategies. This article explores the taxonomy of modernization tasks proposed by Raquel Pau and the hybrid methodology that pairs OpenRewrite’s deterministic, type-aware recipes with the semantic reasoning power of large language models. Concrete demonstrations illustrate how upgrade plans are calculated from Maven metadata, how skills orchestrate recipe execution followed by LLM-driven semantic fixes, and how a structured DSL extracted from legacy code guides a full rewrite while preserving contracts and enabling incremental delivery.
Deterministic versus Non-Deterministic Transformations
Modernization tools fall into two broad categories. Deterministic tools always produce the identical output for a given input. Renaming a method, updating a package import, or replacing a deprecated Spring API are deterministic operations. OpenRewrite belongs to this category: it operates on a lossless semantic tree that retains type attribution obtained from the compiler, applies visitor-based recipes, and preserves the original formatting of the source. Because the transformation is deterministic, recipes can be unit-tested with high confidence and executed at scale across hundreds of repositories without surprise.
Non-deterministic problems admit many correct answers. Generating documentation, extracting the business intent of a filter, or inventing an idiomatic Spring Security configuration from a set of JAX-RS name-binding annotations are examples. Large language models excel here because they reason over patterns and can synthesize higher-level constructs that do not exist in the original code. The cost, however, is variability, the need for evaluation harnesses, and a tendency to hallucinate when internal libraries or proprietary APIs are outside the model’s training distribution.
OpenRewrite’s limitations are the mirror image of its strengths. It cannot perform runtime analysis; dependency injection and reflection mean that many object relationships become visible only after the application starts. It cannot invent new semantic abstractions; a mechanical translation of JAX-RS filters into Spring filters often leaves residual compilation errors or suboptimal configurations that require human or LLM insight. Cross-language migration is outside its design scope.
Coding agents partially compensate for these gaps by using pattern-based reasoning and by iterating until the project compiles. Yet they lack default type attribution, suffer from context-window constraints, and generate large volumes of tokens before reaching a stable state. The rational strategy is therefore hybrid: apply deterministic recipes first to shrink the problem, then invoke the LLM only for the residual semantic work.
Three Levels of Modernization
Pau organizes modernization into three progressively more demanding levels.
Upgrades remain inside the same framework family. A Spring Boot 3.3 application is moved to Spring Boot 4, simultaneously updating transitive dependencies such as Jackson and JUnit. Because Spring’s release train is not strictly linear and because organizations maintain internal frameworks with their own release cadences, a simple “latest version” recipe is insufficient. An upgrade-plan engine inspects Maven metadata, calculates a sequence of compatible intermediate steps, and emits a series of small, reviewable pull requests. Each step leaves the application in a buildable state. Tanzu’s Application Advisor exposes this capability via the cf repo upgrade plan and cf repo apply upgrade plan commands, demonstrating that continuous, low-risk upgrades can be embedded in CI pipelines.
Migrations change the underlying framework while preserving language and runtime. The canonical example is Jakarta JAX-RS to Spring Boot. Name-binding annotations that attach filters to resources have no direct counterpart; authentication filters must become Spring Security configurations; repositories must acquire @Repository annotations. The recommended skill therefore first executes the OpenRewrite recipes that perform the mechanical rewrite and any accompanying Spring Boot upgrade, then hands control to the coding agent to resolve remaining compilation errors and to map name-binding semantics onto Spring constructs. The result is both more complete and far less expensive in tokens than asking an unconstrained LLM to rewrite the entire application.
Full rewrites discard the original implementation while preserving contracts. A COBOL batch program that sorts records by date and amount must become a Spring Batch job that reads the same input format, produces identical output, and respects the same database schema if one is involved. Because legacy systems rarely possess comprehensive tests, the process begins by extracting a catalog of user stories, then a structured domain-specific language description of inputs, outputs, and processing steps. Only after the human reviewer validates the generated tests and the semantic model does the agent emit Spring code, typically seeded by a skeleton obtained from start.spring.io. Incremental delivery is essential: large monolithic rewrites cannot be reviewed or risk-managed in a single step.
Orchestrating OpenRewrite and LLM Agents
Three integration mechanisms allow a coding agent to invoke OpenRewrite without saturating its context window. Local MCP servers expose the rewrite CLI so that only the command and its concise output enter the conversation. Skills package the same CLI invocation and are loaded only when the agent decides the skill is relevant. Prompts can be registered with a remote MCP server, yet they must be fully present in every conversation and therefore scale poorly for complex migrations.
The hybrid skill for a JAX-RS migration therefore looks roughly as follows: calculate the upgrade plan that includes the JAX-RS recipes, execute the recipes, collect residual compilation diagnostics, and finally apply semantic transformations that replace name-binding filters with Spring Security and Spring MVC constructs. Because the deterministic phase has already performed the bulk of the mechanical work, the LLM operates on a far smaller residual problem and produces higher-quality results.
For full rewrites the skill is organized into three explicit phases. Phase one extracts a user-story catalog and stores it under version control so that subsequent runs reuse the analysis. Phase two materializes a structured DSL for a chosen story, including acceptance criteria, data models, and external contracts. Phase three generates the Spring implementation and correlating tests. Human validation remains mandatory; the agent cannot be trusted to invent missing requirements or to decide whether an original implementation was correct.
Practical Demonstrations and Organizational Implications
In the upgrade demonstration a Spring Petclinic application on Boot 3.3 is analyzed; the engine proposes coordinated upgrades of Spring Boot, Jackson and JUnit; successive apply steps produce small, reviewable diffs that leave the project green after each commit. In the migration demonstration a pure JAX-RS Petclinic is transformed: OpenRewrite rewrites the bulk of the code, the agent resolves compilation issues caused by signature changes, and name-binding annotations disappear in favor of proper Spring Security configuration. In the rewrite demonstration a simple COBOL sorter is analyzed, a single user story and its DSL are generated, a Spring Batch project is scaffolded, and the resulting executable produces byte-for-byte identical output.
The organizational payoff is standardization. When every application can be moved to a common Spring Boot baseline with low friction, teams share libraries, security configurations and operational practices. Token consumption drops dramatically because deterministic recipes eliminate the majority of mechanical work. Evaluation of non-deterministic skills becomes feasible because the residual problem set is smaller and more homogeneous.
Conclusion
Modernization success depends on matching the tool to the nature of the transformation. OpenRewrite supplies precision, testability and scalability for deterministic changes. Large language models supply the semantic insight required for migrations and rewrites. A carefully designed hybrid that keeps the LLM outside the hot path of routine upgrades, that constrains its context to residual problems, and that forces explicit contracts for full rewrites yields both higher quality and lower cost. Organizations that adopt this disciplined approach can keep large application portfolios current without sacrificing reviewability or operational safety.