The shape of Java 21
The shape of Java 21
First released in 1995, Java has lived for nearly 30 years. This post lays out Java's flow, the LTS cycle, and a few features in Java 21 that carry real weight, all on a factual basis. We also briefly touch on neighbors like GraalVM, Kotlin, and Scala.
1. About Java
The language came from James Gosling's team at Sun Microsystems. The first official release was May 1995 (JDK 1.0 in January 1996). When Oracle acquired Sun in 2010, Oracle became the primary backer. The same year OpenJDK was open-sourced under GPLv2 + Classpath Exception (started in 2007, effectively standardized around 2010), and most distributions today (Oracle JDK, Eclipse Temurin, Amazon Corretto, Microsoft Build of OpenJDK, Azul Zulu) are based on OpenJDK.
2. Compilation and execution
.java source is compiled by javac into bytecode (.class). The JVM reads that bytecode and runs it, accelerating frequently-used methods at runtime via JIT (Just-In-Time) compilation to native code. The HotSpot JVM is effectively the default implementation.
3. The LTS cycle
Since 2017 Java has shipped a new release every six months, with selected versions designated as LTS (Long-Term Support):
| Version | Release | Note |
|---|---|---|
| 8 | 2014 | Lambdas, Stream API. The longest-lived LTS. |
| 11 | 2018 | LTS. Includes var local inference (from 10). |
| 17 | 2021 | LTS. Records GA, sealed classes. |
| 21 | 2023-09 | LTS. Virtual threads, pattern matching for switch. |
| 25 | 2025-09 | Planned LTS. |
Versions in between (12 ~ 16, 18 ~ 20, 22 ~ 24) are six-month feature releases.
4. Java 21 features
record (preview in 14, GA in 16) — an immutable data class in one line:
record Point(int x, int y) {}
var p = new Point(3, 4);
p.x(); // auto-generated accessor
sealed class (preview in 15, GA in 17) — declares which subclasses are permitted. Combined with pattern matching, the compiler catches missing branches:
sealed interface Shape permits Circle, Square {}
record Circle(double r) implements Shape {}
record Square(double s) implements Shape {}
pattern matching for switch (GA in 21):
double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Square q -> q.s() * q.s();
};
}
When sealed and record are used together, the compiler catches missing cases without needing default.
virtual threads (Project Loom, GA in 21) — one OS thread cooperatively runs many virtual threads, automatically yielding (unmounting) on I/O waits:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> { /* blocking I/O is fine */ });
}
What used to cost tens of MB per thread reportedly drops to KB. This is often the path to scale concurrency while keeping blocking code as is.
5. GC
The JVM offers several GCs:
- G1 — default through 21. Balance of throughput and pause times.
- ZGC (15+) and Shenandoah (12+) — low-latency GCs aiming for sub-millisecond pauses.
- Parallel and Serial — traditional simple GCs. Suitable for small environments.
The choice depends on workload. ZGC often comes up when you need a large heap with low pauses.
6. Other paths
| Language | First release | Note |
|---|---|---|
| Java | 1995 | JVM standard. |
| Kotlin | 2011 (1.0 in 2016) | JetBrains. JVM / Native / JS / Wasm. Officially recommended for Android (2017). |
| Scala | 2004 | EPFL. Functional + OO. Strong in academic and data circles. |
| Groovy | 2003 | Dynamic JVM language. Lives on as the Gradle build script language. |
| Clojure | 2007 | Lisp-family JVM language. |
Kotlin encodes nullability into the type system and has coroutines as part of the standard library. Scala 3 (2021) goes deeper into macros and the type system. All three compile to JVM bytecode and can use Java libraries directly.
7. GraalVM
A polyglot runtime announced by Oracle Labs. The first official GA was in 2019 (public work began in 2018). Two common uses:
- JIT replacement — plug GraalVM JIT into HotSpot for more aggressive optimization.
- Native Image — AOT compile to a static native binary. Startup time in tens of milliseconds, memory in a few MB. Reflection and dynamic class loading require extra configuration.
Spring Boot 3 (2022), Micronaut, and Quarkus all support GraalVM Native Image as a first-class citizen.
8. Builds and version management
# Same on Windows and macOS
./gradlew build # macOS, Linux, Git Bash
gradlew.bat build # Windows cmd
./gradlew bootRun # run a Spring Boot app
Java version management:
- SDKMAN! (macOS, Linux) —
sdk install java 21.0.5-tem - Scoop and winget (Windows) —
winget install EclipseAdoptium.Temurin.21.JDK
Pointing JAVA_HOME correctly is the starting point of consistent builds.
9. Common pitfalls
JDK vs JRE — from 21 onward Oracle effectively stopped shipping a separate JRE. Just grab the JDK.
The module system (JPMS, 9+) coexisting with the classpath — adopting module-info.java should be considered carefully against library compatibility.
virtual thread + synchronized — at the 21 timeframe, synchronized blocks can pin a virtual thread to its OS thread, undoing the benefits. ReentrantLock is recommended. Improvements are in progress for 25.
Reflection and Native Image — AOT compilation needs to know dynamic behavior up front. Metadata like reflect-config.json or @Reflective is required.
The GC tuning trap — measure the workload before reaching for many flags. Defaults often suffice.
Closing thoughts
The combination of Java 21's record, sealed, pattern matching for switch, and virtual threads is a major leap for modern Java — comparable in weight to the lambda + Stream API jump in the Java 8 era. Virtual threads in particular hold strong operational value: they scale concurrency while leaving blocking code untouched. GraalVM Native Image is gradually being adopted in places where startup time and memory matter most (serverless, CLIs).
Next
- python-async
- rust-for-tauri
OpenJDK, OpenJDK GitHub, Oracle Java Documentation, JEPs (JDK Enhancement Proposals), GraalVM, Eclipse Temurin, SDKMAN!, Kotlin, and Scala are the references.