In this article, we will explore 10 best practices in Java 21 and provide complete examples to help you understand them better.
1️⃣ Use Virtual Threads for High-Performance Concurrency
Why?
Traditional threads in Java are heavyweight because they map directly to OS threads. Virtual Threads in Java 21 are lightweight, enabling the JVM to manage millions of threads efficiently.
✅ Best Practice: Use Virtual Threads when handling many concurrent tasks, such as handling HTTP requests or database queries.
🔹 Complete Example: Virtual Threads in Action
import java.util.concurrent.Executors;
public class VirtualThreadExample {
public static void main(String[] args) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("Executing: " + Thread.currentThread());
});
}
} // Executor closes automatically
}
}
💡 Use Virtual Threads when working with large numbers of independent tasks.
2️⃣ Use StringTemplate
for Cleaner String Formatting
Why?
Concatenating strings using +
or String.format()
can be error-prone and hard to read. Java 21 introduces StringTemplate
, making string formatting safer and more readable.
✅ Best Practice: Use StringTemplate instead of String.format()
.
🔹 Complete Example: Using StringTemplate
for Formatting
import static java.lang.StringTemplate.STR;
public class StringTemplateExample {
public static void main(String[] args) {
String name = "Amit";
int age = 30;
// ✅ Java 21 String Template
String message = STR."Hello, my name is \{name} and I am \{age} years old.";
System.out.println(message);
}
}
💡 This reduces the chances of formatting errors and makes code more readable.
3️⃣ Use Sequenced Collections for Ordered Data Structures
Why?
Before Java 21, collections like List
, Set
, and Map
had inconsistent order-handling methods. Sequenced Collections ensure predictable ordering.
✅ Best Practice: Use Sequenced Collections when working with ordered data.
🔹 Complete Example: Using SequencedSet
for Ordered Elements
import java.util.SequencedSet;
import java.util.LinkedHashSet;
public class SequencedCollectionExample {
public static void main(String[] args) {
SequencedSet<String> cities = new LinkedHashSet<>();
cities.add("Delhi");
cities.add("Mumbai");
cities.add("Bangalore");
// ✅ Retrieves first and last elements
System.out.println("First: " + cities.getFirst());
System.out.println("Last: " + cities.getLast());
}
}
💡 Use SequencedCollection
when you need ordered elements with easy first/last access.
4️⃣ Use Pattern Matching in switch
for Cleaner Code
Why?
Instead of writing multiple instanceof
checks and explicit casts, Java 21 allows Pattern Matching in switch
statements.
✅ Best Practice: Use Pattern Matching in switch
for type-safe, readable code.
🔹 Complete Example: Using Pattern Matching in switch
public class PatternMatchingExample {
static void process(Object obj) {
switch (obj) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer: " + i);
case null -> System.out.println("Null value");
default -> System.out.println("Unknown type");
}
}
public static void main(String[] args) {
process("Hello");
process(42);
process(null);
}
}
💡 This makes switch
statements more concise and eliminates redundant casting.
5️⃣ Use record
for Simple Data Structures
Why?
Creating POJOs (Plain Old Java Objects) for simple data often requires boilerplate code. record
simplifies this by auto-generating constructors, getters, equals, hashcode, and toString()
.
✅ Best Practice: Use record
for immutable data structures.
🔹 Complete Example: Defining and Using a record
public record User(String name, int age) {}
public class RecordExample {
public static void main(String[] args) {
User user = new User("Amit", 30);
// ✅ Access fields without getters
System.out.println(user.name());
System.out.println(user.age());
}
}
💡 Records are immutable and ideal for DTOs (Data Transfer Objects).
6️⃣ Use ScopedValues
for Thread-Safe Context Management
Why?
Java 21 introduces ScopedValue
as a faster alternative to ThreadLocal
for passing context between threads.
✅ Best Practice: Use ScopedValue
to store request-related information efficiently.
🔹 Complete Example: Using ScopedValue
in a Multi-Threaded Environment
import java.lang.ScopedValue;
public class ScopedValueExample {
static final ScopedValue<String> USERNAME = ScopedValue.newInstance();
public static void main(String[] args) {
ScopedValue.where(USERNAME, "Amit").run(() -> {
System.out.println("Current User: " + USERNAME.get());
});
}
}
💡 Use ScopedValue
instead of ThreadLocal
to avoid memory leaks.
7️⃣ Use Record Patterns for Object Destructuring
Why?
Instead of manually extracting values from records using getters, Java 21 allows Record Patterns for destructuring objects.
✅ Best Practice: Use record patterns for cleaner object decomposition.
🔹 Complete Example: Using Record Patterns
record Point(int x, int y) {}
public class RecordPatternExample {
static void printPoint(Point p) {
switch (p) {
case Point(int x, int y) -> System.out.println("X: " + x + ", Y: " + y);
}
}
public static void main(String[] args) {
printPoint(new Point(5, 10));
}
}
💡 This simplifies object pattern matching in switch
cases.
8️⃣ Use Structured Concurrency for Managing Threads
Why?
Handling multiple threads manually is error-prone. Structured Concurrency makes it easier to manage and join tasks.
✅ Best Practice: Use StructuredTaskScope
to manage dependent tasks.
🔹 Complete Example: Using Structured Concurrency
import java.util.concurrent.*;
public class StructuredConcurrencyExample {
public static void main(String[] args) throws InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> task1 = scope.fork(() -> "Task 1 Completed");
Future<String> task2 = scope.fork(() -> "Task 2 Completed");
scope.join();
System.out.println(task1.resultNow());
System.out.println(task2.resultNow());
}
}
}
💡 Use this when managing parallel tasks that depend on each other.
9️⃣ Use Foreign Function & Memory API for Native Code Interoperability
Why?
Interacting with native code in Java required JNI (Java Native Interface), which was complex and error-prone. Java 21 introduces the Foreign Function & Memory API, allowing direct interaction with native memory without JNI.
✅ Best Practice: Use the Foreign Function & Memory API for high-performance native interop.
🔹 Complete Example: Allocating and Accessing Native Memory
import java.lang.foreign.*;
import java.nio.charset.StandardCharsets;
public class ForeignMemoryExample {
public static void main(String[] args) {
try (Arena arena = Arena.openConfined()) { // Confined Arena for memory management
MemorySegment segment = arena.allocate(100); // Allocate 100 bytes
// Store a string in native memory
String message = "Hello from Java 21!";
segment.asByteBuffer().put(message.getBytes(StandardCharsets.UTF_8));
// Read back the message
byte[] bytes = new byte[message.length()];
segment.asByteBuffer().get(bytes);
System.out.println(new String(bytes, StandardCharsets.UTF_8));
} // Memory is automatically freed when arena closes
}
}
💡 This is useful for high-performance applications that interact with C libraries.
🔟 Use String.repeat()
Instead of Loops for Repeating Text
Why?
Repeating strings in Java used to require manual loops or StringBuilder
. Java 11 introduced repeat()
, which eliminates unnecessary iterations.
✅ Best Practice: Use .repeat()
instead of manually looping.
🔹 Complete Example: Generating Repeated Strings
public class RepeatExample {
public static void main(String[] args) {
// ❌ Old way: Using loops
String stars = "";
for (int i = 0; i < 5; i++) {
stars += "*";
}
System.out.println(stars); // Output: *****
// ✅ New way: Using repeat()
System.out.println("*".repeat(5)); // Output: *****
}
}
💡 Great for formatting text-based UI components, logs, and output formatting.
✅ Summary: Top 10 Java 21 Best Practices
✅ Use Virtual Threads for scalable concurrency.
✅ Use StringTemplate
for cleaner string formatting.
✅ Use SequencedCollection
for ordered data.
✅ Use Pattern Matching in switch
for cleaner code.
✅ Use record
for immutable data structures.
✅ Use ScopedValue
instead of ThreadLocal
.
✅ Use Record Patterns for object destructuring.
✅ Use Structured Concurrency for better thread management.
✅ Use Foreign Function & Memory API for native interop.
✅ Use .repeat()
for simple string repetition.
Java 21 simplifies coding, improves performance, and enhances concurrency. These best practices will help you write faster, safer, and cleaner Java applications.
💬 Which Java 21 feature are you excited to use? Let me know! 🚀🔥
Comments
Post a Comment
Leave Comment