Setting Up Java 21: JDK Options, Tooling, and IDE Configuration
Choosing a JDK Distribution
Java 21 is open source (OpenJDK). Multiple vendors distribute binaries, all built from the same source with identical features. Choose based on your support requirements:
| Distribution | Vendor | Free | LTS Support | Notes |
|---|---|---|---|---|
| Eclipse Temurin | Adoptium | Yes | 2028+ | Recommended for most developers |
| OpenJDK | Oracle/Community | Yes | 6 months only | Reference implementation, no LTS |
| Oracle JDK | Oracle | Yes (NFTC) | 2031 | Identical to OpenJDK since Java 17 |
| Amazon Corretto | AWS | Yes | 2030+ | Good for AWS deployments |
| Microsoft Build of OpenJDK | Microsoft | Yes | Long-term | Good for Azure deployments |
| Azul Zulu | Azul | Yes/Paid | Long-term | Includes Zing JVM variant |
| GraalVM | Oracle | Yes | Long-term | Adds native-image, polyglot |
Recommendation: Eclipse Temurin for local development; match your cloud provider’s JDK for production (Corretto on AWS, Microsoft OpenJDK on Azure).
Installing Java 21
Option 1: SDKMAN! (Recommended — Manages Multiple JDKs)
# Install SDKMAN! (Linux/macOS)
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
# List available Java 21 distributions
sdk list java | grep 21
# Install Eclipse Temurin 21
sdk install java 21.0.3-tem
# Set as default
sdk default java 21.0.3-tem
# Switch to Java 21 in current shell only
sdk use java 21.0.3-tem
# Verify
java -version
# openjdk version "21.0.3" 2024-04-16 LTS
SDKMAN! is the best tool for local development — it lets you switch between Java 8, 11, 17, and 21 per project.
Option 2: Homebrew (macOS)
# Install Eclipse Temurin 21
brew tap homebrew/cask-versions
brew install --cask temurin21
# Verify
java -version
# If you have multiple JDKs, switch with:
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
Option 3: Direct Download
Download the JDK from adoptium.net and set JAVA_HOME manually:
# Linux/macOS — add to ~/.bashrc or ~/.zshrc
export JAVA_HOME=/path/to/jdk-21
export PATH=$JAVA_HOME/bin:$PATH
Verify Installation
java -version
# openjdk version "21.0.x" ...
javac -version
# javac 21
# Check available GC options
java -XX:+PrintFlagsFinal -version | grep UseZGC
Maven Configuration
pom.xml — Compiler Plugin
<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
</plugins>
</build>
Use <release>21</release> (not <source> + <target>) — release enforces the correct bootstrap classpath and prevents using APIs removed in Java 21.
Enabling Preview Features in Maven
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<release>21</release>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- Also needed for running tests with preview features -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
Spring Boot Maven Setup
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<properties>
<java.version>21</java.version>
</properties>
Spring Boot 3.2+ sets the compiler release automatically when java.version=21 is set. Enable virtual threads with one property:
# application.properties
spring.threads.virtual.enabled=true
Gradle Configuration
build.gradle (Groovy DSL)
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
tasks.withType(JavaCompile).configureEach {
options.release = 21
}
build.gradle.kts (Kotlin DSL)
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
tasks.withType<JavaCompile>().configureEach {
options.release.set(21)
}
Enabling Preview Features in Gradle
tasks.withType<JavaCompile>().configureEach {
options.release.set(21)
options.compilerArgs.add("--enable-preview")
}
tasks.withType<Test>().configureEach {
jvmArgs("--enable-preview")
}
tasks.withType<JavaExec>().configureEach {
jvmArgs("--enable-preview")
}
Gradle Toolchains (Auto-Download JDK)
Gradle 7.6+ can download and use the correct JDK automatically:
// build.gradle.kts
plugins {
id("java")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
vendor.set(JvmVendorSpec.ADOPTIUM) // Eclipse Temurin
}
}
Run with: ./gradlew build — Gradle downloads Java 21 if not found.
IntelliJ IDEA Configuration
Project SDK
- File → Project Structure → Project
- Set SDK to Java 21 (Add if not listed:
+→Add JDK→ point to your JDK 21 path) - Set Language level to
21(or21 - Previewfor preview features)
Module Language Level
For preview features per-module:
- File → Project Structure → Modules
- Select module → Sources tab
- Set Language level to
21 - Preview
Compiler Settings
File → Settings → Build, Execution, Deployment → Compiler → Java Compiler:
- Set Project bytecode version to
21 - For preview: add
-source 21 --enable-previewto Additional command line parameters
Recommended Plugins
- Java 21 Support — already built into IntelliJ IDEA 2023.2+
- SonarLint — static analysis
- CheckStyle-IDEA — code style enforcement
IntelliJ IDEA 2023.2 and later has native Java 21 support including virtual thread debugging.
VS Code Configuration
Extensions to Install
Java Extension Pack (Microsoft)
├── Language Support for Java (Red Hat)
├── Debugger for Java (Microsoft)
├── Java Test Runner (Microsoft)
├── Maven for Java
└── Project Manager for Java
settings.json
{
"java.configuration.runtimes": [
{
"name": "JavaSE-21",
"path": "/path/to/jdk-21",
"default": true
}
],
"java.compile.nullAnalysis.mode": "automatic",
"java.format.settings.url": "https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml"
}
Verifying Your Setup
Create Hello.java:
// Hello.java — tests Java 21 features
import java.util.List;
void main() { // Unnamed class + instance main (preview)
// Sequenced Collections (final)
var list = List.of("Java", "21", "rocks");
System.out.println(list.getFirst()); // "Java"
System.out.println(list.getLast()); // "rocks"
// Pattern matching for switch (final)
Object o = 42;
String result = switch (o) {
case Integer i when i > 0 -> "positive: " + i;
case Integer i -> "non-positive: " + i;
default -> "not an integer";
};
System.out.println(result); // "positive: 42"
// Virtual thread (final)
Thread.ofVirtual().start(() ->
System.out.println("Hello from virtual thread!")
);
}
Compile and run:
# Preview features needed for unnamed class (void main)
javac --enable-preview --release 21 Hello.java
java --enable-preview Hello
Expected output:
Java
rocks
positive: 42
Hello from virtual thread!
Virtual Thread Debugging in IntelliJ
IntelliJ IDEA 2023.2+ displays virtual threads in the debugger thread list. Set a breakpoint inside a virtual thread — the debugger shows the virtual thread, its carrier thread, and the full stack trace.
Enable virtual thread tracking:
# JVM flag for virtual thread monitoring
java -Djdk.tracePinnedThreads=full --enable-preview MyApp
This prints a stack trace whenever a virtual thread is pinned (stuck on its carrier thread due to a synchronized block or native call).
Common Setup Mistakes
Using <source> and <target> instead of <release> — release correctly enforces the bootstrap classpath; source/target alone allow accidentally using APIs that don’t exist in Java 21.
Forgetting --enable-preview at runtime — classes compiled with --enable-preview also require --enable-preview at runtime. The JVM refuses to load them otherwise.
Running Java 21 bytecode on Java 17 — Java 21 .class files have bytecode version 65.0. Java 17’s JVM rejects them. Check your JAVA_HOME in production matches your compile target.
Using String Templates in new projects — JEP 430 was withdrawn from Java 23. It is unavailable in Java 23+ and will return in a redesigned form. Do not build on it.
Key Takeaways
- Eclipse Temurin is the recommended free JDK distribution; SDKMAN! is the best tool for managing multiple JDKs
- Use
<release>21</release>in Maven, not<source>+<target>— it enforces the correct platform API constraints - Spring Boot 3.2+ fully supports Java 21; enable virtual threads with
spring.threads.virtual.enabled=true - Gradle toolchains auto-download the correct JDK — no manual JAVA_HOME setup needed per project
- IntelliJ IDEA 2023.2+ has native Java 21 support including virtual thread visualization in the debugger
- Preview features require
--enable-previewat both compile time AND runtime — wire it into your test runner too
Next: Pattern Matching for switch (JEP 441) — eliminate instanceof chains and manual casts with type-safe, exhaustive switch expressions.