OpenRewrite Learning (Part 1): Basics and Principles

Addo Zhang
5 min readDec 21, 2024

--

Recently, my work has revolved around system transformation, involving framework and platform migrations. I came across OpenRewrite, an intriguing tool, and decided to compile some learning notes to document the learning process. I plan to explore further and update these articles over time, though the exact number of parts remains uncertain.

As software projects grow increasingly complex and the pace of version iterations and new technology adoption accelerates, engineers often face the challenge of unifying code styles, performing batch refactoring, or migrating versions across thousands of lines of code. Manual efforts are time-consuming, error-prone, and prone to omissions. In large-scale scenarios, this process resembles a major engineering project, which has led to the emergence of Migration Engineering.

Migration Engineering is a systematic engineering practice widely used for tech stack updates, code modernization, architecture optimization, and large-scale change management. By combining automation tools and processes, it ensures quality while reducing manual intervention, improving efficiency, lowering risks, and maintaining consistency in codebases during technological evolution.

- From ChatGPT

To achieve precise, controllable, and repeatable automated refactoring, various tools and frameworks have emerged. Among them, OpenRewrite has garnered significant attention. It facilitates large-scale code refactoring in a structured, sustainable, and extensible manner, thereby boosting productivity and code quality for development teams.

Let’s start by understanding the basics of OpenRewrite and the principles behind it.

What is OpenRewrite?

OpenRewrite is an open-source automated refactoring framework developed by Moderne. Its goal is to enable structured rewriting of code through a series of composable “recipes” (Recipes, as per official terminology). These recipes act as refactoring rules. For clarity and alignment with the official documentation, we’ll refer to them as recipes. OpenRewrite operates beyond simple global search-and-replace functionality, utilizing the Lossless Semantic Tree (LST)for semantic-level code modifications.

Use Cases

Before diving into its principles, let’s explore common scenarios where OpenRewrite proves its value:

  1. Standardizing Code Styles and Formatting Rules: When teams decide to adopt a unified code style (e.g., specific naming conventions, removing unnecessary comments, excessive whitespace, or line breaks), OpenRewrite rules can automate repository-wide cleanup.
  2. Batch Upgrading Dependencies and Framework Migrations: For example, imagine managing a vast microservices architecture and needing to upgrade from Spring Boot 2.x to 3.x. This could involve synchronizing hundreds of dependencies across dozens or even hundreds of repositories. OpenRewrite can automate these changes using a single recipe.
  3. Standardizing Logging Frameworks or Security Libraries: When a team decides to deprecate a logging library or security framework, OpenRewrite ensures accurate identification and replacement of related code throughout the repository.
  4. Modernizing Legacy Code: For instance, converting early Java constructs (like anonymous inner classes) to modern lambda expressions or replacing older collection frameworks with stream APIs injects new features and coding standards into legacy projects.

Key Features

  • Syntax and Semantic Analysis: OpenRewrite parses code based on LST, ensuring precise modifications.
  • Extensible Recipes: A wealth of built-in and extensible rule sets automate common code transformations. Recipes can be combined for more complex refactorings.
  • Wide Language and Framework Support: Supports languages like Java, Kotlin, and Groovy; data formats like XML, YAML, Properties, JSON, and ProtoBuf; build tools like Maven and Gradle; and frameworks like Spring, Quarkus, Micronaut, and Jakarta.

If recipes are the heart of OpenRewrite’s automated refactoring, LST is the cornerstone that ensures precise semantic structure during transformations.

Overview of Core Principles

OpenRewrite’s core philosophy is abstracting and modeling source code into a structured format (LST) to enable precise, controllable modifications at the semantic level.

Lossless Semantic Tree (LST)

Unlike the traditional Abstract Syntax Tree (AST), OpenRewrite uses a Lossless Semantic Tree (LST), a data structure that preserves all details of the original source code without loss. This includes syntax, semantics, formatting, whitespace, line breaks, and comments — details typically lost during simple AST-based transformations.

Recipes and Visitors

One of OpenRewrite’s key concepts is the Recipe. A recipe defines how code is analyzed or transformed, encompassing one or more sets of logic. Recipes range from simple (e.g., adding a suffix to a class name) to complex (e.g., migrating from one framework to another).

Under the hood, recipes often employ the Visitor pattern. Visitors traverse all nodes in the LST, applying rules to matching elements. This process acts like a “code inspector” that modifies specific parts of the code as needed.

LST Lifecycle

OpenRewrite provides parsers for different programming languages and data formats, converting source files into LST structures.

Here’s how OpenRewrite operates locally:

  1. OpenRewrite generates an LST in memory, representing the current state of the codebase.
  2. The specified recipes transform the LST.
  3. The transformed LST is converted back to plain text and overwrites the original files.
  4. After successful persistence, the process ends. OpenRewrite does not retain any data between runs.
  5. Running a new recipe regenerates an entirely new LST, incorporating any prior changes from the modified files.

Getting Started

OpenRewrite offers many ready-to-use recipes, both official and community-contributed. The Recipe Catalog provides documentation for these recipes. OpenRewrite integrates seamlessly with Maven, Gradle, and IDEs like IntelliJ IDEA Ultimate 2024.1+.

Example: Modifying a Method Name

Here’s an example using a Java project:

Original Code:

@GetMapping("/hi")
public String hello() {
return "hello, devops";
}

Configuration (rewrite.yml):

---
type: specs.openrewrite.org/v1beta/recipe
name: com.atbug.demo.ChangeMethodNameExample
displayName: Change method name example
recipeList:
- org.openrewrite.java.ChangeMethodName:
methodPattern: com.example.tekton.test.TektonTestApplication hello(String)
newMethodName: greeting

Result:

After running the recipe, the method hello is renamed to greeting:

@GetMapping("/hi")
public String greeting() {
return "hello, devops";
}

Conclusion

This article introduces OpenRewrite’s fundamental concepts and principles, with a simple example. In real-world scenarios, recipes can be flexibly combined or customized to address specific business needs, supporting scalable and controllable code migrations. By integrating OpenRewrite into CI/CD pipelines, you can achieve automated migrations with continuous validation.

In future posts, I will delve deeper into LST, recipes, visitors, and writing custom solutions to meet enterprise-specific needs. Stay tuned!

--

--

Addo Zhang
Addo Zhang

Written by Addo Zhang

CNCF Ambassador | LF APAC OpenSource Evangelist | Microsoft MVP | SA and Evangelist at https://flomesh.io | Programmer | Blogger | Mazda Lover | Ex-BBer

No responses yet