Build tools like Gradle are an essential, often overlooked part of software development and delivery. They fetch dependencies, invoke compilers, invoke tests, and finally package and deploy the final artifacts. Because build tools are so intertwined with code, their versions, plugins, and configuration must be kept modernized just like product code itself. This is never more true, or more challenging to get right, than when build tool configuration is itself code.
Gradle presently supports two Domain Specific Languages (DSL) for describing the assembly and testing of our software projects: Groovy DSL and Kotlin DSL. The Kotlin DSL has quite a few advantages over the older and more established Groovy DSL, chief among them static typing and improved IDE support. The Kotlin DSL has also had the significant disadvantage that, unlike Groovy, OpenRewrite did not support it. As users of the Kotlin DSL this has been a long-time sore spot—indeed some of Moderne’s internal repositories switched to the Groovy DSL just to get back OpenRewrite support.
At long last users no longer have to make that concession because OpenRewrite now supports Gradle Kotlin DSL! It has been quite the journey over the last two years to get to this milestone. In this blog post, we’ll dive into a bit of where we came from and how we got here. So let’s go!
Impasse: Complexity of extracting the code generation step
OpenRewrite has had support for the Kotlin language and for Gradle Groovy for years, so why was it so hard to bring them together? It was largely due to the hidden complexities scattered throughout the build files. This exists even in apparently simple build files. Consider this basic Gradle Kotlin script:

Functions like api are defined in the Java plugin, which isn’t directly referenced in this file. Instead, they are available because the org.openrewrite.build.language-library plugin applies the Java plugin transitively. Effectively, the plugins block acts a lot like a separate buildscript for your buildscript.
To fully parse the file with type attribution, the plugins block would have to be interpreted, and the plugin IDs converted into GAV (Group:Artifact:Version) coordinates through a custom dependency resolution process. However, to actually make all of the plugin-provided functions available, Gradle needs to perform a code generation step. This step produces type-safe accessor functions, which in turn enable the superior IDE user experience, a major selling point of the Kotlin DSL.
Unfortunately it isn’t easy to use Gradle’s own implementation of this code generation. The code generation is based on internal APIs that aren’t expected to be stable between Gradle versions, all nestled deeply into 50-100MB Gradle distributions. The complexity of extracting/emulating the code generation step was the fundamental deterrent that put off this highly desired feature for so long. Given IntelliJ is able to do this, OpenRewrite can find a way to do it as well.
Shortcut: Landmarks to guide the way
After being stalled for so long, we began to consider whether a partial or incremental solution might still offer value—even if it couldn’t provide full coverage. Strong type attribution is a foundational reason that OpenRewrite recipes can be precise and easy to author. But do we really need it for the most common build script editing recipes?
Our type attribution on the Groovy side is already imperfect. Given Groovy’s more dynamic and loosely typed nature, that’s expected. Yet, we still manage to write effective recipes for upgrading dependencies and plugins. With that in mind, we decided to skip the code generation step entirely.
For a recipe author working with partially-typed Gradle scripts, sometimes you get lucky—type attribution is available and helps guide the way. Other times, it’s missing, and you have to rely on structural landmarks instead.
For example, the Upgrade Transitive Dependencies recipe has to manipulate the constraints block, but that function doesn’t end up with type attribution. Still, a recipe can reasonably infer that it is dealing with the correct function if the relevant plugin is applied and the block is nested within dependencies. It’s not foolproof, as multiple plugins can define a dependencies function, but it’s often accurate enough. In the same way, Kotlin DSL scripts without full type attribution can still be navigated effectively using similar landmark-based heuristics.
Integration: GradleParser includes the Kotlin parser
If you look inside the GradleParser class, you’ll see that it delegates parsing to either the Groovy or Kotlin parsers based on the file extension:

Inside rewrite-gradle-plugin we get the real classpath from Gradle process currently running the build:

This allows for partial type attribution and is ready for the code generation step, should we ever implement it. Within unit tests Gradle Kotlin scripts can be declared with the org.openrewrite.gradle.Assertions.buildGradleKts() function.
Uplift: Polyglot adaptation for OpenRewrite recipes
Parser support is great, but it’s ultimately a tool that takes no action until wielded within a recipe. The most critical existing Gradle recipes focus on dependency management—adding, upgrading, and reporting dependencies. These are commonly used both on their own and as building blocks for other high-impact recipes, such as Spring Boot Migrations and Find and Fix Vulnerable Dependencies.
We considered splitting these recipes into separate Groovy and Kotlin versions, but that would have cluttered the recipe namespace and hurt discoverability. Still, there’s no such thing as a GroovyAndKotlinVisitor, so the path to making these recipes truly polyglot isn’t straightforward.
Two key features of the OpenRewrite framework come together to make this Polyglot adaptation possible, if still laborious.
First, the Groovy and Kotlin LSTs both inherit from the Java LST. So a JavaVisitor can be used to traverse and manipulate Groovy and Kotlin LSTs alike. There are still meaningful differences between these LSTs and is inconvenient for manipulating language-specific syntax constructs. If a JavaVisitor wants to manipulate Groovy map literals—sometimes used in dependency declarations and plugin applications—it has to manually do type checking, casting, and traversal normally handled by GroovyVisitor.
The second polyglot-supporting feature is the GradleDependency trait. Helpfully, this shared component provides many operations common to Gradle recipes. A dependency might appear as a Groovy map literal or a String or a Kotlin function, and most recipes don’t care about which. They just want to update the version number. GradleDependency encapsulates the syntactic specifics so that recipes need not concern themselves.
Upside: JVM recipes improving Kotlin Gradle scripts
As of now these recipes have been enlightened to support Gradle Kotlin DSL:
- AddDependency
- ChangeDependency
- RemoveDependency
- UpgradeDependencyVersion
- UpgradeTransitiveDependencyVersion
This benefits not only those individual recipes, but all recipes that depend on them. So now you can expect all the most popular JVM migration recipes to also make changes to Kotlin Gradle scripts.

If you maintain a recipe module, get access to these enhancements by upgrading to OpenRewrite 8.50.0 / rewrite-recipe-bom 3.6.0. If you notice bugs let us know—or get started contributing a patch!—in the OpenRewrite community slack or GitHub issue tracker.
If these changes help you automate away your dependency headaches we’d love to hear about it during one of our weekly Code Remix Weekly streams, or even better in person at Code Remix Summit in Miami, Florida coming May 12-14, 2025.