Java 21 Features: Virtual Threads, Records, Pattern Matching & More | TKTips.org

Java 21 Features: Virtual Threads, Records, Pattern Matching & More | TKTips.org
Practical Tech Knowledge for Developers
September 2023
Java 21
10 min read
Programming, Java, LTS

Java 21 Features: A Complete Beginner-Friendly Breakdown

Java 21 is a landmark release as it’s a Long-Term Support (LTS) version, packed with features that significantly improve developer productivity and application performance. In this guide, we’ll explore the most important features with clear explanations and visual diagrams to help you understand how they work.

Key Takeaways

  • Virtual Threads revolutionize concurrency with lightweight threads
  • Records provide concise syntax for immutable data carriers
  • Pattern Matching simplifies conditional extraction of data from objects
  • Sequenced Collections bring predictable iteration order
  • String Templates simplify string formatting and interpolation

Virtual Threads (JEP 444)

Lightweight threads that dramatically reduce the effort of writing, maintaining, and debugging concurrent applications

Virtual threads are lightweight threads that help to write high-throughput concurrent applications with the simple thread-per-request style. They’re managed by the JVM rather than the operating system, making them cheap to create and efficient to use.

How Virtual Threads Work
Platform Thread (Carrier Thread)
VT 1 (Running)
VT 2 (Blocked)
VT 3 (Ready)
VT 4 (Running)
VT 5 (Blocked)
VT 6 (Ready)
Another Platform Thread
VT 7 (Running)
VT 8 (Ready)

Virtual threads are mounted on platform threads. When a virtual thread blocks, the platform thread can run another virtual thread.

Traditional platform threads are expensive to create and context-switch because they’re mapped 1:1 to OS threads. Virtual threads are managed by the JVM, allowing you to have millions of threads without overwhelming the system.

Code Example

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { // Submit 10,000 tasks – each gets its own virtual thread for (int i = 0; i < 10000; i++) { int taskId = i; executor.submit(() -> { Thread.sleep(1000); // Simulate work System.out.println(“Task “ + taskId + ” completed”); return taskId; }); } } // executor.close() is called automatically, waiting for all tasks

Records (JEP 440)

Transparent carriers for immutable data with automatically derived methods

Records provide a compact syntax for declaring classes that are transparent holders for shallowly immutable data. They automatically generate boilerplate code like constructors, accessors, equals(), hashCode(), and toString().

Record Structure
Record Declaration
public record Person(
    String name,
    int age,
    String email
) {}
Immutable
Auto-generated methods
Concise syntax

Traditional Class vs Record

Traditional ClassRecord
50+ lines of code for a simple data carrier1 line of code
Manually implement equals(), hashCode(), toString()Automatically generated
Mutable by defaultImmutable by design
Verbose constructorCompact canonical constructor

Code Example

// Traditional class (simplified)
public class PersonClass { private final String name; private final int age; // Constructor, getters, equals, hashCode, toString… }

// Record (Java 21)
public record Person(String name, int age, String email) { // Compact constructor with validation public Person { if (age < 0) { throw new IllegalArgumentException(“Age cannot be negative”); } } }

// Usage
Person person = new Person(“Alice”, 30, [email protected]); System.out.println(person.name()); // Accessor: name() not getName() System.out.println(person); // Auto-generated toString()

Pattern Matching (JEP 441)

Simplify conditional extraction of components from objects

Pattern matching allows you to conditionally extract components from objects with a single, concise syntax. It extends the instanceof operator to declare a pattern variable and automatically casts it.

Pattern Matching Flow
Object obj = “Hello Java 21”;
β†’
Type: String
if (obj instanceof String s)
β†’
Can use ‘s’ as String
switch (obj) { case String s -> … }
β†’
Pattern matching in switch

Evolution of Pattern Matching in Java

Java VersionPattern Matching Capability
Before Java 16Manual instanceof checks with explicit casting
Java 16Pattern matching for instanceof
Java 17Pattern matching for switch (preview)
Java 21Pattern matching for switch (final)

Code Example

// Old way: instanceof with casting
if (shape instanceof Circle) { Circle circle = (Circle) shape; System.out.println(“Radius: “ + circle.getRadius()); } else if (shape instanceof Rectangle) { Rectangle rect = (Rectangle) shape; System.out.println(“Area: “ + rect.getWidth() * rect.getHeight()); }

// New way: Pattern matching with switch
switch (shape) { case Circle c when c.getRadius() > 0 -> System.out.println(“Circle with radius: “ + c.getRadius()); case Rectangle r -> System.out.println(“Rectangle area: “ + r.getWidth() * r.getHeight()); case null -> System.out.println(“Shape is null”); default -> System.out.println(“Unknown shape”); }

// Record patterns – destructuring records
if (obj instanceof Point(int x, int y)) { System.out.println(“Point at (“ + x + “, “ + y + “)”); }

Other Notable Features in Java 21

Sequenced Collections

New interfaces to represent collections with a defined encounter order. Provides uniform APIs for accessing first and last elements.

SequencedCollection<String> seq = new ArrayList<>(); seq.addFirst(“first”); // New method seq.getLast(); // New method

String Templates

Simplify string interpolation and composition with embedded expressions. Safer than concatenation or StringBuilder.

String name = “Alice”; int age = 30; String message = STR.“Hello \{name}, you are \{age} years old”;

Scoped Values

Share immutable data within and across threads. Better alternative to ThreadLocal for virtual threads.

final static ScopedValue<String> USER = ScopedValue.newInstance(); ScopedValue.where(USER, “alice”).run(() -> { // USER is accessible here });