Migrate to Java 21
Upgrading to Java 21 unlocks new language features such as sequenced collections and pattern matching for switch expressions and statements. It also forces you to replace APIs that have been deprecated for removal. Doing this by hand across an enterprise codebase is tedious and error-prone, since many of the changes are mechanical rewrites scattered across thousands of files.
The UpgradeToJava21 recipe applies these changes automatically: it adopts the new APIs, replaces deprecated ones with their modern equivalents, and updates build files and CI configuration to target Java 21.
In this guide, we will show you how to run this recipe on the Moderne Platform or with the Moderne CLI.
Codebases on Java 8, 11, or 17 are all valid starting points. UpgradeToJava21 includes the earlier upgrade recipes transitively, so you do not need to chain multiple migrations.
What this recipe does
UpgradeToJava21 is a composite recipe that bundles many smaller transformations. Some of the most visible changes this recipe makes to your code include:
- Adopt
SequencedCollection– replaces index-based access patterns with sequenced-collection methods likegetFirst(),getLast(),addFirst(), andremoveLast(). - Adopt switch pattern matching (JEP 441) – converts type-checking
if/elsechains into pattern-matching switch statements. - Convert
new URL(String)toURI.create(String).toURL()– replaces a constructor that has been deprecated for removal. - Prefer
Locale.of(..)overnew Locale(..)– uses the static factory introduced in Java 19.
Beyond the source code, the recipe also updates Maven and Gradle build files to target Java 21, bumps build plugins to Java 21-compatible versions, and updates GitHub Actions workflows. For the complete list of sub-recipes, see the recipe catalog page.
Example
Here is a small class before and after UpgradeToJava21 runs:
import java.net.URL;
import java.util.List;
import java.util.Locale;
class CountryService {
Locale locale = new Locale("en", "US");
String firstCountry(List<String> countries) {
return countries.get(0);
}
URL endpoint() throws Exception {
return new URL("https://example.com/api");
}
}
import java.net.URI;
import java.net.URL;
import java.util.List;
import java.util.Locale;
class CountryService {
Locale locale = Locale.of("en", "US");
String firstCountry(List<String> countries) {
return countries.getFirst();
}
URL endpoint() throws Exception {
return URI.create("https://example.com/api").toURL();
}
}
Running the recipe
- Moderne Platform
- Moderne CLI
- Sign in to your Moderne tenant or app.moderne.io.
- (Optionally) Use the Organization selector to scope the run to the repositories you want to upgrade.
- Search for the
Migrate to Java 21recipe (Moderne Platform link). - Click Dry run.
For a step-by-step walkthrough of the Moderne Platform UI, see Quickstart: Using the Moderne Platform.
Make sure you have built or downloaded Lossless Semantic Trees (LSTs) for the repositories you want to upgrade.
This recipe has no required configuration options. Users of Moderne can run it via the Moderne CLI.
You will need to have configured the Moderne CLI on your machine before you can run the following command.
mod run . --recipe UpgradeToJava21
If the recipe is not available locally, then you can install it using:
mod config recipes jar install org.openrewrite.recipe:rewrite-migrate-java:3.34.0
Reviewing and committing the changes
Running the recipe never modifies your source repositories directly. Instead, the changes are presented as a diff that you can inspect before deciding what to commit. Review them with whatever workflow fits your team, then either commit them from the Moderne Platform or use commands like mod git in the CLI to commit the changes.
Additional reading
- Tracking migrations – use data tables and visualizations to track the rollout of a Java 21 upgrade across many repositories.