Java 21: The LTS Release That Changes Everything
Why Java 21 Is Different
Every third Java release is an LTS — Long-Term Support. Java 21 is the fourth LTS after Java 8, 11, and 17. But unlike previous LTS releases, which were largely incremental, Java 21 delivers features that fundamentally change how you write concurrent code, how the JVM manages memory and GC, and how Java competes with dynamic languages for expressiveness.
Three years of Project Loom work lands as Virtual Threads — production-ready, requiring zero framework changes for most Spring Boot or Jakarta EE applications. Pattern Matching for switch closes the gap with languages like Kotlin and Scala for data-oriented programming. Generational ZGC achieves sub-millisecond GC pauses at any heap size.
This is not an incremental release. It is the platform that Java will be migrated to over the next several years.
Java Release Cadence: A Quick Primer
flowchart LR
J8["Java 8\nLTS\nMar 2014"]
J11["Java 11\nLTS\nSep 2018"]
J17["Java 17\nLTS\nSep 2021"]
J21["Java 21\nLTS\nSep 2023"]
J25["Java 25\nLTS\nSep 2025"]
J8 -->|"4 years"| J11
J11 -->|"3 years\n6 feature releases"| J17
J17 -->|"2 years\n4 feature releases"| J21
J21 -->|"2 years\n4 feature releases"| J25
Since Java 9, Oracle ships a new Java version every 6 months. Non-LTS versions receive security patches for 6 months only. LTS versions (every 4th release starting Java 17, every 2nd starting Java 25) receive long-term support from Oracle and OpenJDK vendors.
Java 21 support timeline:
- Oracle Java 21: Premier Support until September 2026, Extended until September 2031
- Eclipse Temurin (Adoptium): Updates through at least 2028
- Red Hat OpenJDK: Updates through 2031
All 15 JEPs at a Glance
flowchart TD
subgraph Final["9 Final Features — Production Ready"]
F1["JEP 431\nSequenced Collections"]
F2["JEP 439\nGenerational ZGC"]
F3["JEP 440\nRecord Patterns"]
F4["JEP 441\nPattern Matching for switch"]
F5["JEP 444\nVirtual Threads"]
F6["JEP 449\nDeprecate Win32 x86"]
F7["JEP 451\nPrepare: Dynamic Agents"]
F8["JEP 452\nKEM API"]
end
subgraph Preview["5 Preview Features — Require --enable-preview"]
P1["JEP 430\nString Templates"]
P2["JEP 443\nUnnamed Patterns & Variables"]
P3["JEP 445\nUnnamed Classes & Instance Main"]
P4["JEP 446\nScoped Values"]
P5["JEP 453\nStructured Concurrency"]
end
subgraph Incubator["1 Incubator"]
I1["JEP 448\nVector API"]
end
The Five Features That Matter Most
1. Virtual Threads (JEP 444) — The Big One
Java’s threading model has been 1:1 since Java 1.0: one Java thread = one OS thread. OS threads are expensive — 1–2 MB of stack memory each, slow context switching, hard limit around 10,000 per machine.
Virtual Threads break this constraint. They are JVM-managed, lightweight threads — thousands can share a single OS thread. Blocking a virtual thread on I/O unmounts it from the OS thread without parking the OS thread. The OS thread picks up another virtual thread immediately.
flowchart TB
subgraph Old["Platform Thread Model (pre-21)"]
PT1["Java Thread 1"] --> OT1["OS Thread 1 (1-2 MB)"]
PT2["Java Thread 2"] --> OT2["OS Thread 2 (1-2 MB)"]
PT3["Java Thread N"] --> OT3["OS Thread N (1-2 MB)"]
end
subgraph New["Virtual Thread Model (Java 21)"]
VT1["Virtual Thread 1"]
VT2["Virtual Thread 2"]
VT3["Virtual Thread 3"]
VT4["Virtual Thread ...N"]
CT1["Carrier/OS Thread 1"]
CT2["Carrier/OS Thread 2"]
VT1 & VT2 --> CT1
VT3 & VT4 --> CT2
end
Real impact: Tomcat, Jetty, and Spring Boot all work with virtual threads via a single property change. Netflix reported 10× throughput improvement for I/O-bound workloads.
2. Pattern Matching for switch (JEP 441)
Before Java 21, type-based dispatch required chains of instanceof checks and explicit casts:
// Java 16 and earlier
if (shape instanceof Circle) {
Circle c = (Circle) shape;
return Math.PI * c.radius() * c.radius();
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return r.width() * r.height();
} else {
throw new IllegalArgumentException("Unknown shape");
}
Java 21 — one expression, type-safe, exhaustiveness checked by the compiler:
// Java 21
double area = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
};
3. Record Patterns (JEP 440)
Destructure nested records in a single pattern:
record Point(int x, int y) {}
record Shape(String color, Point center) {}
// Without record patterns (Java 16–20)
if (obj instanceof Shape s) {
int x = s.center().x();
int y = s.center().y();
System.out.println(s.color() + " at " + x + "," + y);
}
// With record patterns (Java 21)
if (obj instanceof Shape(String color, Point(int x, int y))) {
System.out.println(color + " at " + x + "," + y);
}
4. Sequenced Collections (JEP 431)
A consistent API for first/last element access and reverse iteration — something the JDK inexplicably lacked for 30 years:
List<String> list = List.of("a", "b", "c");
list.getFirst(); // "a" — replaces list.get(0)
list.getLast(); // "c" — replaces list.get(list.size()-1)
list.reversed(); // ["c", "b", "a"] view
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("x", 1); map.put("y", 2); map.put("z", 3);
map.firstEntry(); // x=1
map.lastEntry(); // z=3
map.reversed(); // z→y→x view
5. Generational ZGC (JEP 439)
ZGC (introduced Java 11) already delivered sub-millisecond GC pauses. Generational ZGC adds young/old generation separation — the same optimization that made G1 efficient — while keeping pause times under 1ms. Production benchmarks show 4× throughput improvement on applications with high object allocation rates.
java -XX:+UseZGC -XX:+ZGenerational -jar myapp.jar
What Changed Since Java 17
Java 17 was the previous LTS. Developers moving from Java 17 to Java 21 gain:
| Feature | Java 17 | Java 21 |
|---|---|---|
| Virtual Threads | Not available | Final (JEP 444) |
| Pattern Matching for switch | Preview (JEP 406) | Final (JEP 441) |
| Record Patterns | Not available | Final (JEP 440) |
| Sequenced Collections | Not available | Final (JEP 431) |
| Generational ZGC | Not available | Final (JEP 439) |
| Structured Concurrency | Not available | Preview (JEP 453) |
| Scoped Values | Not available | Preview (JEP 446) |
| KEM API | Not available | Final (JEP 452) |
Developers moving from Java 11 additionally gain:
- Sealed classes (Java 17, JEP 409)
- Records (Java 16, JEP 395)
instanceofpattern matching (Java 16, JEP 394)- Text blocks (Java 15, JEP 378)
- Switch expressions (Java 14, JEP 361)
var(Java 10, JEP 286)
Preview Features — What They Mean
Preview features ship with a Java release but are not final. They require --enable-preview at compile and runtime, and their API may change in subsequent versions. They are not meant for production code — they are an explicit invitation to provide feedback.
# Compile with preview features
javac --enable-preview --release 21 MyClass.java
# Run with preview features
java --enable-preview MyClass
In Maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
In Java 21, these are preview: String Templates, Unnamed Patterns and Variables, Unnamed Classes and Instance Main Methods, Scoped Values, Structured Concurrency.
Note: String Templates (JEP 430) was removed in Java 23 for a redesign. Do not build production systems on it.
Adoption Decision Tree
flowchart TD
Q1{"Are you on Java 17 LTS?"}
Q1 -->|Yes| Upgrade["Upgrade to Java 21 —\nno breaking changes for most apps"]
Q1 -->|No — Java 11| J11["Check dependency compatibility\nthen upgrade"]
Q1 -->|No — Java 8| J8["Significant migration work —\nsee Migration Guide article"]
Upgrade --> VT{"Is your app I/O-bound?\nHTTP, DB, file, external APIs?"}
VT -->|Yes| EnableVT["Enable Virtual Threads immediately\nSpring Boot: spring.threads.virtual.enabled=true"]
VT -->|No — CPU-bound| SkipVT["Skip Virtual Threads —\nno benefit for CPU work"]
EnableVT --> ZGC{"Need low-latency GC?\nReal-time, financial, gaming?"}
ZGC -->|Yes| EnableZGC["Switch to Generational ZGC"]
ZGC -->|No| G1["Stay with G1 (default) —\ngood for most apps"]
Key Takeaways
- Java 21 is an LTS release with long-term vendor support through at least 2031
- 9 final features are production-ready with no flags required
- Virtual Threads is the single highest-impact feature for most server-side applications — it requires minimal code changes for Spring Boot, Jakarta EE, and Quarkus apps
- Pattern Matching for switch and Record Patterns eliminate entire classes of boilerplate — adopt them immediately for new code
- Generational ZGC delivers sub-millisecond pauses — evaluate it if your app is GC-sensitive
- Preview features require
--enable-preview— use them in experiments, not production - String Templates (JEP 430) was withdrawn in Java 23 — do not adopt it
Next: Setting Up Java 21: JDK Options, Tooling, and IDE Configuration — install Java 21, configure your build tools, and enable preview features correctly.