Migrate to a newer version of Java
Upgrading to a modern Java release lets your codebase adopt new language features and clears out APIs that have been deprecated for removal. Most of these transformations are mechanical individually, but applying them consistently across thousands of files by hand is slow and easy to get wrong.
Moderne's Java upgrade recipes are composite recipes that apply these changes automatically, and they are transitive: the recipe for a given target includes the upgrades for every earlier version. In most cases you should migrate straight to the latest long-term support (LTS) release, Java 25. If a dependency, runtime, or internal policy requires you to stop at an intermediate LTS such as Java 17 or Java 21, see Targeting an earlier LTS below.
In this guide, we will show you how to run these recipes on the Moderne Platform or with the Moderne CLI.
Codebases on Java 8, 11, 17, or 21 are all valid starting points. UpgradeToJava25 includes the earlier upgrade recipes transitively, so you do not need to chain multiple migrations.
What this recipe does
UpgradeToJava25 is a composite recipe that bundles many smaller transformations. Some of the most visible changes this recipe makes to your code include:
- Migrate
public static void main(String[] args)to instancevoid main()– takes advantage of JEP 512 to drop thestaticmodifier and the unusedargsparameter from main methods. - Migrate
System.out.printto Java 25 IO utility class – swapsSystem.out.print(ln)calls for the newIO.print(ln)methods. - Use
Process#waitFor(Duration)– replaces the legacy(timeout, TimeUnit)overload with the type-safeDurationoverload. - Replace unused variables with underscore – uses the
_placeholder for unused lambda parameters and exception variables (JEP 456).
Beyond the source code, the recipe also updates Maven and Gradle build files to target Java 25 and bumps build plugins to Java 25-compatible versions. For the complete list of sub-recipes, see the recipe catalog page.
Example
Here is a small class before and after UpgradeToJava25 runs:
import java.util.concurrent.TimeUnit;
public class Greeter {
public static void main(String[] args) throws Exception {
Process p = new ProcessBuilder("echo", "hi").start();
p.waitFor(5, TimeUnit.SECONDS);
System.out.println("done");
}
}
import java.io.IO;
import java.time.Duration;
public class Greeter {
void main() throws Exception {
Process p = new ProcessBuilder("echo", "hi").start();
p.waitFor(Duration.ofSeconds(5));
IO.println("done");
}
}
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 25recipe (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 UpgradeToJava25
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.38.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 use the Moderne Platform's commit options or the mod git CLI commands to push the changes across the affected repositories.
Targeting an earlier LTS
Most teams should migrate straight to Java 25. If you need to stop at an intermediate long-term support release, run the recipe for that target instead. Each one is also transitive, so it includes the upgrades for all earlier versions up to its target. Run it the same way as described in Running the recipe above, substituting the recipe shown below.
- Java 21
- Java 17
The UpgradeToJava21 recipe adopts new APIs such as sequenced collections and pattern matching for switch, replaces APIs that have been deprecated for removal, and updates build files and CI configuration to target Java 21.
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();
}
}
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.38.0
On the Moderne Platform, search for the Migrate to Java 21 recipe (Moderne Platform link).
The UpgradeToJava17 recipe adopts records, sealed classes, instanceof pattern matching, and text blocks, replaces deprecated APIs with their modern equivalents, and updates build files to target Java 17. For codebases coming from Java 8, it also adds explicit dependencies for J2EE libraries like JAXB that are no longer bundled with the JDK.
class Greeter {
String describe(Object obj) {
if (obj instanceof String) {
String s = (String) obj;
return String.format("string of length %d", s.length());
}
return "unknown";
}
}
class Greeter {
String describe(Object obj) {
if (obj instanceof String s) {
return "string of length %d".formatted(s.length());
}
return "unknown";
}
}
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 UpgradeToJava17
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.38.0
On the Moderne Platform, search for the Migrate to Java 17 recipe (Moderne Platform link).
Additional reading
- Tracking migrations – use data tables and visualizations to track the rollout of a Java upgrade across many repositories.