Getting Started With Java 21

Java 21 overview
Overview the key features and changes since Java 17 (with code examples), download the right production-ready builds of the JDK 21 and get to know the tooling support of Java 21 (Maven, Gradle, IntelliJ IDEA and Eclipse).

Introduction

JDK 21, the open-source reference implementation of Java SE Platform version 21, was released on 19 September 2023.

It will be considered a long-term support (LTS) release from most vendors, the previous version having this kind of support being JDK 17. You can overview the key features and changes from Java 11 to Java 17 in the following article.

Production-ready builds of JDK 21 can be downloaded from the following sources:

  • Eclipse Temurin (successor of AdoptOpenJDK): Provides prebuilt OpenJDK binaries using an open source build & test infrastructure, for more many platforms (such as Linux, Windows and macOS).

  • Azul Zulu: provides many certified builds of OpenJDK for a wide array of platforms.

  • Oracle JDK: Commercial Oracle branded builds of the JDK. Free to use in production and free to redistribute, at no cost, under the Oracle No-Fee Terms and Conditions (NFTC) License.


Key features since Java 17

Record Patterns

Record patterns provide a concise way to unpack nested values from record objects.

record Point(int x, int y) {}
static void printSum(Object obj) {
    // the x and y properties are extracted/unpacked from obj, similar to Destructuring in JavaScript
    if (obj instanceof Point(int x, int y)) {
        System.out.println(x+y);
    } else {
        System.out.println("Not a Point");
    }
}
public static void main(String[] args) {
    printSum(new Point(1, 2));
    printSum(null);
}

Output:

3
Not a Point

Pattern Matching for switch

In Java 21, switch became more expressive and safe by covering more cases and allowing pattern matching.

static String patternSwitchTest(Object obj) {
    return switch (obj) {
        // The null check can now be handled inside the switch statement
        case null -> "null";
        // `Integer i` is a pattern. This case label will be matched if obj is of type Integer (and thus not with an equality test)
        case Integer i -> String.format("int: %d", i);
        case Long l -> String.format("long: %d", l);
        case Double d -> String.format("double: %f", d);
        // An additional boolean expression (guard) can now be specified with when clauses. This is referred to as guarded case labels.
        case String s when s.isBlank() -> "Blank String";
        case String s -> String.format("String: %s", s);
        default -> obj.toString();
    };
}
public static void main(String[] args) {
    System.out.println(patternSwitchTest(3.14d));
    System.out.println(patternSwitchTest("text"));
    System.out.println(patternSwitchTest(null));
    System.out.println(patternSwitchTest("   "));
}

Output:

double: 3.140000
String: text
null
Blank String

With enums

sealed interface Day permits WorkingDay, WeekendDay {}
enum WorkingDay implements Day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY}
enum WeekendDay implements Day {SATURDAY, SUNDAY}
static String patternSwitchWithEnumTest(Day day) {
    return switch (day) {
        case WeekendDay weekendDay -> "Not a working day";
        // Case constants can use qualified names of enum constants even if the switch selector expression is not of the enum type
        case WorkingDay.FRIDAY -> "Last working day";
        // We will get an error if we don't cover all cases in the switch: exhaustiveness is checked
        default -> "Working day";
    };
}
public static void main(String[] args) {
    System.out.println(patternSwitchWithEnumTest(WorkingDay.MONDAY));
    System.out.println(patternSwitchWithEnumTest(WeekendDay.SATURDAY));
    System.out.println(patternSwitchWithEnumTest(WorkingDay.FRIDAY));
}

Output:

Working day
Not a working day
Last working day

Sequenced Collections

New collection interfaces were introduced in Java 21 that allows accessing and processing their elements in a well defined encounter order: SequencedCollection, SequencedMap and SequencedSet.

public static void main(String[] args) {
    // `SequencedCollection` is implemented by `List` and `Deque`, and it's extended by the `SequencedSet` interface
    SequencedCollection<String> collection = Arrays.asList("first value", "second value", "last value");
    // `SequencedCollection` has many methods: addFirst, addLast, getFirst, getLast, removeFirst, removeLast
    System.out.print("collection.getLast(): ");
    System.out.println(collection.getLast());

    // `SequencedMap` is implemented by `LinkedHashMap` and extended by the `SortedMap` interface
    SequencedMap<String, String> map = new LinkedHashMap<>();
    map.putFirst("first key", "first value");
    map.putLast("last key", "last value");
    System.out.print("map: ");
    System.out.println(map);

    // `SequencedSet` is implemented by `LinkedHashSet` and extended by the `SortedSet` interface
    SequencedSet<String> sequencedMap = new LinkedHashSet<>(map.sequencedValues());
    sequencedMap.removeLast();
    System.out.print("sequencedMap: ");
    System.out.println(sequencedMap);

    // `sequencedKeySet` and `sequencedValues` are methods defined in the new `SequencedMap` interface
    // `reversed` is a method defined in the new `SequencedSet` and `SequencedMap` interfaces
    SequencedSet<String> reversedSet = map.sequencedKeySet().reversed();
    // `unmodifiableSequencedSet` is a new method defined in the `Collections` utility class
    SequencedSet<String> unmodifiableSequencedSet = Collections.unmodifiableSequencedSet(reversedSet);
    System.out.print("unmodifiableSequencedSet: ");
    System.out.println(unmodifiableSequencedSet);
}

Output:

collection.getLast(): last value
map: {first key=first value, last key=last value}
sequencedMap: [first value]
unmodifiableSequencedSet: [last key, first key]

Virtual Threads

Java 21 introduced Virtual Threads as an alternative way to write concurrent programs. They are lightweight because they use less OS resources compared to traditional Platform Threads.

They can be started using java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor() or with the java.lang.Thread Builder API such as Thread.ofVirtual() ans Thread.startVirtualThread(Runnable)

Other features

A more exhaustive list of features and changes in JDK 21 integrated since JDK 17 can be found here.


IntelliJ IDEA support for Java 21

IntelliJ IDEA added comprehensive support for Java 21 features starting from the 2023.3 release. The latest version can be downloaded from this link.

To create and run your Java 21 project using IntelliJ IDEA - 2023.3 or a more recent version:

  1. From the top bar, select File -> New -> Project...
    new-java-21-project
  2. Choose or download a JDK 21 and create your project
  3. Create a Main class with the following content for testing:
    public class Main {
        record Point(int x, int y) {}
        static void printSum(Object obj) {
            // the x and y properties are extracted from obj in Java 21
            if (obj instanceof Point(int x, int y)) {
                System.out.println(x+y);
            } else {
                System.out.println("Not a Point");
            }
        }
        public static void main(String[] args) {
            printSum(new Point(1, 2));
        }
    }
  4. Run the project (use the play icon next to the main method). You should have 3 printed on the console without any errors.

Eclipse support for Java 21

Full support to new Java 21 language features was added to Eclipse starting from the 2023-12 release. The latest version can be downloaded from this link.

To create and run your Java 21 project using Eclipse IDE for Java Developers - 2023-12 or a more recent version:

  1. From the top bar, select File -> New -> Java Project
    new-java-21-project
  2. Select Configure JREs...
    jre-21-not-added
  3. Select Add... and choose Standard VM. Press Next
    select-jre-type
  4. Select Directory..., browse and choose your JDK 21 installation directory you have downloaded. Press Finish
    add-jre-21
  5. Select the JRE you have just added. Press Apply and Close
    jre-21-added
  6. Enter your project name and select JavaSE-21 as the execution environment JRE. After customizing your project press Finish
  7. Create a Main class with the following content for testing:
    public class Main {
        record Point(int x, int y) {}
        static void printSum(Object obj) {
            // the x and y properties are extracted from obj in Java 21
            if (obj instanceof Point(int x, int y)) {
                System.out.println(x+y);
            } else {
                System.out.println("Not a Point");
            }
        }
        public static void main(String[] args) {
            printSum(new Point(1, 2));
        }
    }
  8. Run the project (right-click on the class name, then select Run As -> Java Application). You should have 3 printed on the console without any errors.

Tooling support for Java 21

Maven

The maven-compiler-plugin support Java 21 compilation using either of the following configuration in the pom.xml:

  <properties
      <maven.compiler.release>21</maven.compiler.release>
      <!-- ... -->
  </properties>

or

  <properties
      <maven.compiler.source>21</maven.compiler.source>
      <maven.compiler.target>21</maven.compiler.target>
      <!-- ... -->
  </properties>

Gradle

Gradle supports Java 21 starting from the 8.4 release.

This version brings support for compiling, testing, and running JVM-based projects.

Support for running Gradle itself with Java 21 is expected in future versions.



Author Image

Soufiane Sakhi is an AWS Certified Solutions Architect – Associate and a professional full stack developer based in Paris, France. He is the creator of Simply-how.com, the My Route Directions Android app, and many open source browser extensions such as YouTube Playlist Helper and Feedly filtering and sorting.