Links

Recipe development environment

Prerequisites, tools, and recommendations for developing with OpenRewrite
This getting started guide covers setting up your development environment for creating your own OpenRewrite recipes.
If you are looking to build OpenRewrite itself instead of a specific recipe, check out our building and developing OpenRewrite guide.

Prerequisites

  • JDK (version 1.8+)
    • A JRE alone is insufficient since OpenRewrite uses compiler internals and tools only found in the JDK
  • Gradle (version 4.0+ ) or Maven (version 3.2+)
  • Text Editor or IDE with Java support

Automatic project setup

The easiest way to get started developing your own recipes is to visit the rewrite-recipe-starter repository and click the "Use this template" button. That template comes already set up with all the necessary dependencies, build configuration, an example recipe, and tests of the example recipe.
If you've chosen to use the template, skip to Recipe Distribution. If you'd prefer to not use the template, please continue with the below instructions.

Manual project setup

Gradle and Maven both provide helpful commands for initializing a new project. Either of these commands will lay out an appropriate directory structure and a basic build.gradle or pom.xml file.
Gradle
Maven
gradle init
mvn -B archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4

Dependencies & dependency management

Rewrite provides a bill of materials (BOM) that, when imported into your build, will manage the versions of any rewrite dependencies that are included within a project.
You can import the bill of materials into either Gradle or Maven and then include concrete dependencies on the various rewrite libraries without specifying their version.
Gradle
Maven
dependencies {
// import Rewrite's bill of materials.
implementation(platform("org.openrewrite.recipe:rewrite-recipe-bom:2.7.1"))
// rewrite-java dependencies only necessary for Java Recipe development
implementation("org.openrewrite:rewrite-java")
// You only need the version that corresponds to your current
// Java version. It is fine to add all of them, though, as
// they can coexist on a classpath.
runtimeOnly("org.openrewrite:rewrite-java-8")
runtimeOnly("org.openrewrite:rewrite-java-11")
runtimeOnly("org.openrewrite:rewrite-java-17")
// rewrite-maven dependency only necessary for Maven Recipe development
implementation("org.openrewrite:rewrite-maven")
// rewrite-yaml dependency only necessary for Yaml Recipe development
implementation("org.openrewrite:rewrite-yaml")
// rewrite-properties dependency only necessary for Properties Recipe development
implementation("org.openrewrite:rewrite-properties")
// rewrite-xml dependency only necessary for XML Recipe development
implementation("org.openrewrite:rewrite-xml")
// lombok is optional, but recommended for authoring recipes
annotationProcessor("org.projectlombok:lombok:latest.release")
// For authoring tests for any kind of Recipe
testImplementation("org.openrewrite:rewrite-test")
testImplementation("org.junit.jupiter:junit-jupiter-api:latest.release")
testImplementation("org.junit.jupiter:junit-jupiter-params:latest.release")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release")
}
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.testSource>17</maven.compiler.testSource>
<maven.compiler.testTarget>17</maven.compiler.testTarget>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>2.7.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- rewrite-java dependencies only necessary for Java Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java</artifactId>
<scope>compile</scope>
</dependency>
<!-- You only need the version that corresponds to your current
Java version. It is fine to add all of them, though, as
they can coexist on a classpath. -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-8</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-11</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-17</artifactId>
<scope>runtime</scope>
</dependency>
<!-- rewrite-maven dependency only necessary for Maven Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-maven</artifactId>
<scope>compile</scope>
</dependency>
<!-- rewrite-yaml dependency only necessary for Yaml Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-yaml</artifactId>
<scope>compile</scope>
</dependency>
<!-- rewrite-properties dependency only necessary for Properties Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-properties</artifactId>
<scope>compile</scope>
</dependency>
<!-- rewrite-xml dependency only necessary for XML Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-xml</artifactId>
<scope>compile</scope>
</dependency>
<!-- lombok is optional, but recommended for authoring recipes -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- For authoring tests for any kind of Recipe -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M9</version>
</plugin>
</plugins>
</build>
rewrite-test uses JUnit 5.
rewrite-java-17, rewrite-java-11 and rewrite-java-8 can happily coexist on the same classpath. At runtime, the appropriate module for the current JDK will be selected.

Set Language Level and Bytecode Level

To enable running on the widest possible range of JDK versions, configure the Java compiler to target Java Language and Bytecode level 1.8:
Gradle
Maven
For Gradle Kotlin
build.gradle.kts
tasks.named<JavaCompile>("compileJava") {
options.release.set(8)
}
For Gradle Groovy
build.gradle
// notice that we allow test source code to be compiled to release level >8
tasks.compileJava {
options.release.set(8)
}
pom.xml
<!-- see https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-source-and-target.html -->
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

Project layout

Having configured the project per these recommendations, you're now able to begin Recipe development. With Gradle and Maven's default project layout, you'll want to put your files in these directories:
  • src/main/java - Recipe implementations
  • src/test/java - Recipe tests
  • (optional) src/main/resources/META-INF/rewrite - YAML files for defining declarative OpenRewrite Recipes
With all of that done, your project setup is complete! You are now ready to create a Recipe.

Recipe distribution

In order for your recipes to be usable by the OpenRewrite build plugins or on app.moderne.io, they have to be published to an artifact repository.

Local publishing for testing

Before you publish your recipe module to an artifact repository, you may want to try it out locally. To do this, on the command line, run gradle publishToMavenLocal (or equivalently gradle pTML). This will publish to your local maven repository, typically under ~/.m2/repository.
Once your artifact is published, you can test this recipe in a separate repository locally by following the instructions in the running your recipes section.

Publishing to artifact repositories

The rewrite-recipe-starter project is configured to publish to Moderne's open artifact repository (via the publishing task at the bottom of the build.gradle.kts file). If you want to publish elsewhere, you'll want to update that task. app.moderne.io can draw recipes from the provided repository, as well as from Maven Central.
Running the publish task will not update app.moderne.io, as only Moderne employees can add new recipes. If you want to add your recipe to app.moderne.io, please ask the team in Slack or in Discord.
These other docs might also be useful for you depending on where you want to publish the recipe:

Running your Recipes

Once your recipe module is published, either locally for testing or to an external artifact repository for broader distribution, you'll need to configure a separate repository to test with (See the Getting Started Guide for more detailed instructions). In the repository you want to test your recipe against, update the build plugins accordingly:
Gradle
Maven
plugins {
id("java")
id("org.openrewrite.rewrite") version("6.8.4")
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
rewrite("[your recipe module's groupId]:[your recipe module's artifactId]:[your recipe module's version]")
}
rewrite {
activeRecipe("[your recipe name]")
}
If testing locally, your rewrite dependency should match the structure of your .m2 folder. For example, if the path to your recipe in the .m2 folder is:
  • ~/.m2/repository/com/yourorg/rewrite-recipe-starter
and the jar in that folder is:
  • rewrite-recipe-starter-0.1.0-dev.25.uncommitted+f58d7fa
then the rewrite dependency should be:
  • rewrite("com.yourorg:rewrite-recipe-starter:0.1.0-dev.25.uncommitted+f58d7fa").
Now you can run your recipe with gradle rewriteRun or gradle rewriteDryRun
If you run into errors when trying to publish and read your recipe locally, try deleting the jars that end with uncommitted+<hash> and then re-build them. Furthermore, if you're getting errors in your IDE when trying to build or run recipes, try restarting the IDE.
<project>
<build>
<plugins>
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>5.23.1</version>
<configuration>
<activeRecipes>
<recipe> [your recipe name] </recipe>
</activeRecipes>
</configuration>
<dependencies>
<dependency>
<groupId> [your recipe module's groupId] </groupId>
<artifactId> [your recipe module's artifactId] </artifactId>
<version> [your recipe module's version] </version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
If testing locally, your rewrite dependency should match the structure of your .m2 folder. For example, if the path to your recipe in the .m2 folder is:
  • ~/.m2/repository/com/yourorg/rewrite-recipe-starter
and the jar in that folder is:
  • rewrite-recipe-starter-0.1.0-dev.25.uncommitted+f58d7fa.jar
then the rewrite dependency should have:
  • A groupId of com.yourorg
  • An artifactId of rewrite-recipe-starter,
  • A version of 0.1.0-dev.25.uncommitted+f58d7fa.
Now you can run your recipe with mvn rewrite:run or mvn rewrite:dryRun

Moderne IntelliJ plugin

Moderne offers a CLI and an IntelliJ plugin that can help speed up the development and testing of recipes. We'd strongly encourage you to check those out while developing recipes.

Next steps