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.

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:1.19.0"))
// 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>1.19.0</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

For your recipes to be usable by the OpenRewrite build plugins or on public.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 ./gradlew publishToMavenLocal (or equivalently ./gradlew 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. public.moderne.io can draw recipes from the provided repository, as well as from Maven Central.
Running the publish task will not update public.moderne.io, as only Moderne employees can add new recipes. If you want to add your recipe to public.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("5.40.0")
}
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.jar
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 ./gradlew rewriteRun or ./gradlew rewriteDryRun
<project>
<build>
<plugins>
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>4.46.0</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-start,
  • A version of 0.1.0-dev.25.uncommitted+f58d7fa.
Now you can run your recipe with mvn rewrite:run or mvn rewrite:dryRun

Next steps