Open Rewrite: Automated Code Refactoring for Java

Have you come across a scenario where you have to apply the same kind of refactoring/code transformation/fix across multiple repositories?

Also, due to shorter release cycles, we are getting new versions of libraries released every week. Updating the versions of frameworks across multiple repositories can be a complex and lengthy process. Doing it manually can be a time-consuming task and could be error-prone.

I came across OpenRewrite which can help us automate such refactoring and code transformations.

Introduction

Open Rewrite is an open-source tool for the automatic refactoring of Java code. It can be used to apply code transformations automatically. It is built based on the concept of recipes. A recipe is a small unit of work i.e. small transformation of code. e.g. renaming a method. It also provides a way to write custom recipes.

Setup

Open Rewrite can be used for both maven and gradle projects. It has plugins for both. We will be using maven plugin for examples.

Add Plugin

Add the openrewrite plugin to your project's pom.xml

<plugin>
  <groupId>org.openrewrite.maven</groupId>
  <artifactId>rewrite-maven-plugin</artifactId>
  <version>4.44.0</version>
</plugin>

Configuring Recipes

Once you have added the plugin, you need to configure the recipes you want to apply. Add the recipes to the activeRecipes list in the plugin configuration.

Simple recipes which do not take any input configurations can be configured as part of the plugin configuration itself.

<configuration>
    <activeRecipes>                
        <recipe>org.openrewrite.java.AddApache2LicenseHeader</recipe>
    </activeRecipes>
</configuration>

The above recipe adds the Apache License header to all java files in the project.

If the recipes you want to apply need any inputs like changing method names, they can be configured using yaml file.

Create a rewrite.yml file in the project root directory. It can contain multiple recipes. This recipe name needs to be added to the active recipes list in the plugin configuration. You can customize the license text in the yml file as below.

---
type: specs.openrewrite.org/v1beta/recipe
name: dev.hashnode.kuldeepsidhu.AddLicenseHeader
displayName: Add license header
recipeList:
  - org.openrewrite.java.AddLicenseHeader:
      licenseText: Copyright ${CURRENT_YEAR} License Header.

Running Recipes

OpenRewrite plugin adds the below goals to the project which can be used to see the available recipes and apply the recipes.

rewrite:discover

This is used to list down all the available recipes. By default, it will bring recipes that are part of the open rewrite plugin. You can also add other dependencies to include more available recipes.

rewrite:dryRun

This can be used to see what changes will be applied to your code once the configured recipes are applied. It will not apply the changes to the files but will create a patch file in the target directory. You can review the changes before applying the changes.

rewrite:run

This will apply all the configured recipes and make the changes to the project files.

JUnit 4 to 5 Migration Example

For migrating all tests in your project, you can make use of JUnit4to5Migration recipe which is already available. This recipe comprises a lot of other smaller recipes to migrate the tests. It is available in rewrite-testing-frameworks module.

You need to add this module as a dependency at the plugin level.

<plugin>
    <groupId>org.openrewrite.maven</groupId>
    <artifactId>rewrite-maven-plugin</artifactId>
    <version>4.44.0</version>
    <configuration>
        <activeRecipes>
<recipe>org.openrewrite.java.testing.junit5.JUnit4to5Migration</recipe>
        </activeRecipes>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.openrewrite.recipe</groupId>
            <artifactId>rewrite-testing-frameworks</artifactId>
            <version>1.37.0</version>
        </dependency>
    </dependencies>
</plugin>

Running mvn rewrite:run will migrate all tests to JUnit 5.

Use Cases

There are many recipes already available for different purposes. If needed, you can also write your custom recipes for the transformation of code. Below are some examples where OpenRewrite can be used to carry out automatic code transformations:

  • Upgrading dependency versions

  • Migration to new version of frameworks

    • JUnit 4 to 5

    • Java 8 to 11

    • Java 11 to 17

    • Spring Boot 2 to 3

  • Patching security vulnerabilities

  • Updating code to comply with new coding standards

Sample code used for this post is available on Github.

I will cover how to write custom recipes in another post.