How to write OpenRewrite recipes with a coding agent
Contents
If you ask a coding agent to replace a deprecated API across 200 repositories, it will try to do the work itself, opening each file, reasoning about the change, and editing it. This is what agents are designed to do: read, grep, edit, rinse and repeat. But that’s a lot of inference to spend making what should be the same change across the repositories, with no guarantee that each pass will come out the same. If you ask it to write an OpenRewrite recipe instead, it only has to reason about the change once. It’s the recipe that does the rest of the work, and it does it the same way every time.
Writing OpenRewrite recipes turns out to be a great use case for generative AI. When they can be declarative, recipes are highly composable. When they need to be imperative, they’re deeply structured, built around known patterns like visitors and templates. And because every recipe can be backed by before-and-after tests, they’re easily testable, keeping AI as honest as possible. Agents are good at this now, and a little context makes them better.
Skills load conventions only when the agent needs them.
We started writing recipes using Claude Code, and pretty quickly had it producing working recipes. But initially, it missed certain conventions like using preconditions, scanning recipes, and language directives in tests, and it wasn’t always sure when to use declarative YAML versus imperative Java. A CLAUDE.md file helped, but it was tied to one repository, and a recipe-authoring subagent didn’t work as well as we’d hoped. Skills were a better fit, because we could encode our conventions once and reuse them everywhere.
What makes skills especially interesting is how they use progressive disclosure.
Despite good documentation and lots of examples, it can still be a lot for any model to hold in context. And that lack of retained knowledge often looked to be the source of the limitations we were running into. But with progressive disclosure, the coding agent reads only what it needs, when it needs it, instead of trying to cram everything into a prompt. That means it can manage context much more efficiently. A skill is just a bundle of text instructions, optionally supported by things like scripts and reference templates, that get loaded contextually as the task unfolds.
Skills are also easy to maintain. Because it’s just text, the skill can live alongside your codebase and documentation. You can version it, improve it, and share it across teams. For us, that meant we could start small and iterate, just like we do with recipes!
Give the agent the conventions before it starts.
Moderne’s create-recipe skill helps agents follow the correct conventions for writing OpenRewrite recipes. It ships with the Moderne CLI and installs for the coding agents the CLI detects:
mod config agent-tools skills installThe skill covers the decisions that matter most, starting with which kind of recipe to write: declarative YAML to compose existing recipes, a Refaster template for a simple expression replacement, or imperative Java for the rest. It also teaches LST immutability, visitor traversal, type matching with MethodMatcher, and import handling, along with tests in the RewriteTest framework that include no-change cases. Because it drives the mod CLI directly, the agent can build and run what it writes.
You can call it with /moderne:create-recipe, but it also triggers on its own when you ask your agent to write a recipe. It’s supported by Claude Code, GitHub Copilot, OpenAI Codex, and several other agents.
A worked example: migrating JBoss to Jetty.
Models have gotten noticeably better at writing recipes over time, so even without the skill, a coding agent can reasonably write a recipe. Before the skill existed, when we were only experimenting with having Claude Code write recipes, we asked it to come up with a recipe that migrates applications from JBoss to Jetty.
Initially, the code came back with too many dependencies and tests missing the //language=java directive, so we had to steer it in the right direction. When the agent started writing a logging migration from scratch, we nudged it toward the existing JBossLoggingToSlf4j recipe to get it composing instead of rebuilding. The agent also decided OpenRewrite couldn’t generate new files, so it proposed leaving that for a human to do manually after the fact. But we told it, “If you need to create a new file using OpenRewrite, you can use a ScanningRecipe.” That got it to rebuild the logic as a two-pass scanning recipe and move on.
Still, within only a couple of hours we had a module with recipes for XML, Java, and resource bindings, a scanning recipe that generates Jetty configuration, a composite YAML recipe tying it together, and tests for all of it. It wasn’t production-ready, with gaps around EJB, clustering, and JMS. But it was a solid starting point for someone who knows those systems to work with an agent to finish off. And now, several of the corrections we had to make by hand are part of what the create-recipe skill teaches up front.
For a video walkthrough, see Build a Recipe Once, Fix Every Repo:
Can a coding agent write an OpenRewrite recipe?
Yes. Current coding agents write working recipes, and they do best with the recipe conventions and a test to pass up front. Moderne’s create-recipe skill supplies both, so the agent writes structured recipes with tests that check the change and the cases that should stay untouched.
A few habits help with any agent.
Whatever agent you use, a few habits made the biggest difference for us:
-
Ask for a plan before any code. Colleagues found that a written implementation plan up front made recipes noticeably more complete.
-
Tell the agent to search the recipe catalog first. Composing existing recipes in YAML beats writing new Java, and agents often don’t look unless you ask.
-
Require tests, including cases where the recipe should change nothing.
-
When the agent says OpenRewrite can’t do something, check before you believe it. The JBoss example nearly lost a feature that way.
Once the recipe works, run it everywhere.
A recipe that passes its tests is ready for real code. To work across many repositories, agent chat (mod <agent> chat) gives the agent the whole Moderne CLI across a synced organization, so it can run recipes on every repository at once, check the results with each repository’s own build, and edit by hand only what the recipes left behind. On the Moderne Platform, recipes run across the repositories in your tenant, and their data tables show what they found or changed.
To try it, install the Moderne CLI, then run mod config agent-tools skills install and ask your agent to write a recipe in one repository, or start mod <agent> chat to work across many. See the recipe authoring docs for more details on writing recipes.
Originally published


