Aligning recipe module versions
When you maintain your own recipe library, you depend on OpenRewrite and Moderne recipe modules – rewrite-java, rewrite-static-analysis, rewrite-spring, and so on. Each of these modules is individually versioned, and any of them may put out a new patch release in between Moderne's biweekly releases, so it is not obvious which combination of versions you should be building against. It is also not obvious which versions are used once your recipes run from the recipe marketplace in the Moderne CLI or the Moderne Platform.
In this guide, we will walk you through using a bill of materials (BOM) to align the modules your recipe library depends on, how the marketplace isolates recipe modules from one another at run time, and where that isolation stops.
Aligning your dependencies with a bill of materials
A bill of materials (BOM) is a published artifact that manages the versions of a related set of modules. You import the BOM once, then declare dependencies on individual modules without specifying their versions.
Moderne publishes io.moderne.recipe:moderne-recipe-bom, which manages the versions of Moderne's recipe modules. It also imports org.openrewrite.recipe:rewrite-recipe-bom, so a single import covers both the open source OpenRewrite modules and Moderne's proprietary ones.
- Gradle
- Maven
dependencies {
// Import Moderne's bill of materials, which also imports rewrite-recipe-bom
implementation(platform("io.moderne.recipe:moderne-recipe-bom:latest.release"))
// Declare recipe modules without a version - the BOM manages them
implementation("org.openrewrite:rewrite-java")
implementation("org.openrewrite.recipe:rewrite-static-analysis")
implementation("io.moderne.recipe:rewrite-spring")
}
<dependencyManagement>
<dependencies>
<!-- Import Moderne's bill of materials, which also imports rewrite-recipe-bom -->
<dependency>
<groupId>io.moderne.recipe</groupId>
<artifactId>moderne-recipe-bom</artifactId>
<version>0.41.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Declare recipe modules without a version - the BOM manages them -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java</artifactId>
</dependency>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-static-analysis</artifactId>
</dependency>
<dependency>
<groupId>io.moderne.recipe</groupId>
<artifactId>rewrite-spring</artifactId>
</dependency>
</dependencies>
Check the latest versions of every OpenRewrite module for the current moderne-recipe-bom version.
Why the BOM versions matter
The versions the BOM manages are the versions Moderne tests together. They are also the versions that are loaded together when your recipe is deployed to the marketplace, and when your recipes are run directly. Building against the same set means what you tested locally is what runs at scale.
If your recipe library only depends on open source OpenRewrite modules, import rewrite-recipe-bom instead. See setting up your recipe development environment for a fuller starter build configuration.
moderne-recipe-bom and the Moderne recipe modules it manages are distributed through the Code Genome Project, which requires authentication. See configuring the Code Genome Project repository for the repository and credential setup.
Matching versions across language ecosystems
Some recipe modules have a counterpart published to another package ecosystem. Recipes that target languages such as JavaScript, TypeScript, C#, Go, and Python are split between a Maven module and an equivalent npm, NuGet, or PyPI package, and both halves need to be on the same version.
The BOM manages only the Maven side, so install the counterpart at the version the BOM resolved for its matching module. For example, io.moderne.recipe:rewrite-angular pairs with the npm package @openrewrite/recipes-angular, and org.openrewrite:rewrite-python pairs with the PyPI package openrewrite.
The latest versions of every OpenRewrite module page publishes ready-made mod config recipes npm install, pip install, and nuget install commands, with each counterpart already pinned to the version of its matching Maven module.
How recipe modules are isolated in the marketplace
The recipe marketplace does not resolve every installed recipe module against one shared set of dependency versions. Each recipe artifact is loaded together with the dependencies it was published with, isolated from the other artifacts in the marketplace.
This isolation is what allows recipe modules to coexist even when they depend on different versions of other recipe modules. Moderne releases most recipe modules on a biweekly cadence, but not every module follows it. The Quarkus recipe modules, for example, release independently and are often behind the current biweekly release, so they may still be built against an older version of a shared module. Isolation means they run against the version they were built for, while the modules from the current release run against theirs.
For your own recipe library, the practical consequence is that you do not need to match your dependency versions to whatever else happens to be deployed in your marketplace. Your recipes run against the versions you published them with, in both the CLI and the Platform.
What the CLI loads for itself
Isolation is not total. The RecipeClassLoader gives each recipe artifact maximum isolation for its own implementation classes, but delegates the core OpenRewrite API types to its parent classloader so that the CLI and your recipe agree on them. Recipe, ExecutionContext, SourceFile, Tree, JavaParser, MethodMatcher, and the org.openrewrite.rpc types that back cross-language recipes are all loaded this way.
Those classes come from the CLI itself rather than from your recipe artifact, which couples your recipe to the CLI release it runs on. To keep the two in step, run your recipes with a CLI version that was released together with the recipe modules in the BOM you built against. Each BOM manages the matching CLI release as io.moderne:moderne-cli for exactly this reason.
How standalone YAML recipes resolve
The exception to this isolation is a declarative YAML recipe that is installed on its own, rather than packaged inside a recipe artifact.
A YAML recipe references the recipes it includes by name, not by artifact version. YAML that is packaged inside a recipe JAR resolves those names against that JAR's classpath, so it stays isolated like any other recipe artifact. A standalone YAML recipe has no classpath of its own, so its references are resolved when the recipe runs, against whatever versions your marketplace currently has installed.
You will run into this in two places:
- The recipe builder: composing a new recipe, or customizing an existing one, produces a YAML recipe. Its sub-recipes are resolved against the versions deployed in your marketplace, which can be newer or older than the versions the original recipe was compiled against.
- YAML you copied from somewhere else: a recipe pasted from a chat message, a docs page, or a colleague carries no version information either. Installing it with
mod config recipes yaml installadds its recipes to your marketplace, where they resolve against the recipe modules you already have installed.
In both cases the recipe can pick up different – newer or older – versions of recipe modules than the ones it was originally written or compiled against.
A standalone YAML recipe is not pinned to the recipe module versions it was written against. Re-test these recipes after you update the recipe modules in your marketplace, since the sub-recipes they resolve to can change. Packaging the YAML into a recipe JAR instead gives it the same version isolation as any other recipe artifact.
Keeping your marketplace up to date
Because standalone YAML recipes resolve against what is deployed, it is worth knowing how deployed versions change:
- Moderne CLI:
mod config recipes upgradere-resolves and reinstalls each installed artifact that was requested at a dynamic version. See curating the recipe marketplace for how to control which recipes your developers see and which versions they get. - Moderne Platform: an administrator re-deploys the recipe artifact, as described in importing external recipes. Deploying with a version of
RELEASEre-resolves to the newest stable release, whileLATESTre-resolves to the newest available build, including snapshots.
Only artifacts requested at a dynamic version – LATEST or RELEASE – are re-resolved. An artifact installed at an exact version stays on that version until you reinstall it with mod config recipes jar install. That is what you want when you are deliberately holding a team on a validated release, but it also means upgrade alone will not move it.
Next steps
- Setting up your recipe development environment: the full build configuration for a new recipe library.
- Writing and installing recipes: publishing your recipe library and deploying it to the Moderne Platform.
- Using multiple versions of a library in a project: depending on several versions of the same library within one recipe library.