🧵
Java 8+

Java 21 Features

Virtual Threads & Record Patterns
💡 Java 21 एक बड़े restaurant जैसा है जिसने अपना पूरा staffing model बदल दिया — पहले हर table के लिए एक dedicated waiter (heavy OS thread) चाहिए होता था, अब एक छोटा flexible staff (virtual threads) लाखों tables को efficiently handle कर लेता है, क्योंकि वो किसी table के "wait" करते वक्त दूसरी table संभाल लेता है।

Java 21 (September 2023) तीसरी major LTS (Long-Term Support) release है Java 17 के बाद — इसमें Project Loom का सबसे बड़ा result, Virtual Threads, stable feature बनकर आया, जो Java में concurrency लिखने का तरीका fundamentally बदल देता है।

सबसे बड़े highlights: Virtual Threads (लाखों lightweight threads), Sequenced Collections (getFirst/getLast/reversed जैसे common methods), Record Patterns (records को destructure करना), और Pattern Matching for switch (आख़िरकार stable, guard conditions के साथ)।

कुछ features अभी भी preview में हैं (String Templates, Structured Concurrency, Unnamed Variables) — मतलब syntax future versions में थोड़ा बदल सकता है, लेकिन concept समझना interview के लिए ज़रूरी है।

// Virtual Thread — lakhon threads, halke wazan
Thread.ofVirtual().start(() -> System.out.println("Lightweight!"));

// Record Pattern — destructuring
record Point(int x, int y) {}
if (obj instanceof Point(int x, int y)) {
  System.out.println(x + ", " + y);
}

// Sequenced Collection
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
System.out.println(list.getFirst() + " " + list.getLast()); // A C
🧵
Java 21 एक बड़े restaurant जैसा है जिसने अपना पूरा staffing model बदल दिया — पहले हर table के लिए एक dedicated waiter (heavy OS thread) चाहिए होता था, अब एक छोटा flexible staff (virtual threads) लाखों tables को efficiently handle कर लेता है, क्योंकि वो किसी table के "wait" करते वक्त दूसरी table संभाल लेता है।
1 / 6
इस page में (8 subtopics)

Traditional Java thread एक OS thread का direct wrapper है — हर thread बनाने में memory (typically ~1MB stack) और OS resources लगते हैं, इसलिए एक JVM में practically कुछ हज़ार threads ही बन पाते हैं बिना system slow किए। High-concurrency applications (जैसे एक web server जो लाखों requests handle करे) इस limit से टकराते हैं।

Virtual thread JVM के अंदर ही manage होता है (OS thread नहीं) — बहुत हल्का है (kilobytes में, MB नहीं), और एक JVM में लाखों virtual threads बन सकते हैं बिना problem के। जब virtual thread block होता है (जैसे I/O wait), JVM उस underlying OS thread को free कर देता है दूसरे virtual threads के लिए — मतलब थोड़े से OS threads (carrier threads) बहुत सारे virtual threads को serve कर सकते हैं।

सबसे अच्छी बात: code लिखने का तरीका बिल्कुल same रहता है — Thread.ofVirtual().start(...) या Executors.newVirtualThreadPerTaskExecutor() use करो, बाकी सब (synchronized, try-catch, normal blocking calls) वैसे ही काम करता है जैसे normal thread में करता था। कोई नया async/reactive syntax सीखना नहीं पड़ता।

// Ek virtual thread banao
Thread.ofVirtual().start(() -> {
  System.out.println("Running on: " + Thread.currentThread());
});

// Ya ek executor se, jaise normal thread pool
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
  for (int i = 0; i < 100_000; i++) {
    executor.submit(() -> {
      Thread.sleep(Duration.ofSeconds(1)); // block hone par bhi thread cheap hai
      return null;
    });
  }
} // 1 lakh threads bhi easily chal jaate hain!
⚠️Common Mistake: Virtual threads CPU-bound काम (जैसे heavy calculations) के लिए फ़ायदा नहीं देते — इनका फ़ायदा तभी है जब thread I/O (network, file, database) का wait करे। CPU-bound tasks के लिए traditional platform threads/parallel streams ही बेहतर हैं।

पुराने Java में List (जिसमें order होता है) और LinkedHashSet/LinkedHashMap (जिनमें भी predictable order होता है) के पास "first element" या "last element" निकालने का कोई common, consistent तरीका नहीं था — List.get(0) vs List.get(size()-1), या अलग-अलग collections में अलग methods।

Java 21 ने तीन नए interfaces दिए: SequencedCollection, SequencedSet, SequencedMap — जो List, LinkedHashSet, LinkedHashMap जैसे ordered collections को common methods देते हैं: getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), और reversed() (जो collection को reversed view में देखता है, बिना copy बनाए)।

List<String> names = new ArrayList<>(List.of("Aman", "Riya", "Zoya"));
System.out.println(names.getFirst());  // Aman
System.out.println(names.getLast());   // Zoya
names.addFirst("Aarav");
System.out.println(names.reversed());  // [Zoya, Riya, Aman, Aarav]

// LinkedHashMap bhi ab yehi methods deta hai:
LinkedHashMap<String, Integer> scores = new LinkedHashMap<>();
scores.put("A", 90);
scores.putFirst("Z", 100); // naya method — sabse pehle daalta hai
💡Tip: reversed() एक "view" return करता है (नया object copy नहीं बनाता) — मतलब अगर original collection change हो, reversed view भी automatically update हो जाता है।

Java 17 में records आए थे data classes के लिए, और Java 16+ में instanceof pattern matching आई थी। Java 21 ने इनको combine कर दिया — अब record के अंदर के fields को सीधा "destructure" (निकाल) कर सकते हो एक instanceof या switch check में, बिना record.field() call किए।

जैसे अगर record Point(int x, int y) है, तो if (obj instanceof Point(int x, int y)) लिखने से, अगर obj एक Point है, तो x और y directly available हो जाते हैं — record के अंदर record भी हो सकता है (nested destructuring), जो deeply nested data structures के लिए बहुत clean code देता है।

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Object obj = new Point(3, 4);

// Record pattern — seedha destructure
if (obj instanceof Point(int x, int y)) {
  System.out.println("x=" + x + ", y=" + y);
}

// Nested record pattern
Object line = new Line(new Point(0, 0), new Point(3, 4));
if (line instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
  System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}

Java 17 में यह preview feature था, Java 21 में आख़िरकार stable/final हो गया। अब switch expression/statement में type-based branching, record destructuring, और guard conditions (when clause) सब एक साथ use कर सकते हो।

null भी अब switch में directly handle हो सकता है (case null ->) — पहले switch null पर NullPointerException देता था, इसलिए अलग से null check करना पड़ता था। Guard condition (when) से एक type-match के अंदर भी extra condition लगा सकते हो, जैसे case Integer i when i > 0 -> ....

Object obj = 42;

String result = switch (obj) {
  case null -> "It's null";
  case Integer i when i > 0 -> "Positive integer: " + i;
  case Integer i -> "Non-positive integer: " + i;
  case String s -> "String: " + s;
  case Point(int x, int y) -> "Point at (" + x + "," + y + ")"; // record pattern bhi
  default -> "Unknown";
};
💡Tip: Sealed classes के साथ combine करो तो compiler खुद confirm कर सकता है कि switch के सारे possible cases cover हो गए ("exhaustiveness") — default case की भी ज़रूरत नहीं पड़ती।

String concatenation (+ से जोड़ना) या String.format() दोनों थोड़े clunky हैं लंबी strings के लिए। String Templates एक नया syntax लाती हैं — STR."Hello \{name}!" जैसा — जिसमें \{...} के अंदर directly Java expressions डाल सकते हो, और वो automatically evaluate होके result में embed हो जाते हैं।

यह एक preview feature है Java 21 में (मतलब --enable-preview flag के साथ compile/run करना पड़ता है, और future versions में change भी हो सकता है) — लेकिन यह दिखाता है Java string handling को कहाँ ले जा रहा है: readable, type-safe, और SQL-injection जैसी चीज़ें भी easily prevent करने वाले custom template processors (जैसे SQL processor) बन सकते हैं।

String name = "Aarav";
int age = 25;

// Purana tareeka:
String s1 = "Hello " + name + ", you are " + age + " years old";
String s2 = String.format("Hello %s, you are %d years old", name, age);

// String Template (preview, Java 21):
String s3 = STR."Hello \{name}, you are \{age} years old";
// Expressions bhi chal sakti hain:
String s4 = STR."Next year you'll be \{age + 1}";
⚠️Common Mistake: यह preview feature है — production code में बिना --enable-preview flag के compile ही नहीं होगा, और syntax future JDK versions में बदल भी सकता है। Interview में concept समझना ज़रूरी है, syntax exactly याद रखना उतना ज़रूरी नहीं।

जब एक task को complete करने के लिए multiple sub-tasks parallel में चलाने पड़ते हैं (जैसे एक user profile fetch करने के लिए अलग-अलग services से data, price और inventory), उन्हें manually manage करना (एक fail हो जाए तो बाकी cancel करना, सबका wait करना, errors handle करना) error-prone होता है।

StructuredTaskScope sub-tasks को एक "structured" तरीके से treat करता है — parent task तब तक खत्म नहीं होता जब तक सारे child tasks (जो उसने start किए) खत्म न हो जाएं, और अगर एक child fail हो, पूरा group automatically cancel हो सकता है (fail-fast)। यह virtual threads के साथ मिलकर concurrent code को बहुत safer और readable बनाता है।

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
  Future<String> user = scope.fork(() -> fetchUser());
  Future<String> orders = scope.fork(() -> fetchOrders());

  scope.join();           // dono ka wait karo
  scope.throwIfFailed();  // agar koi fail hua, exception throw karo

  System.out.println(user.resultNow() + orders.resultNow());
} // scope band hote hi saare threads cleanup ho jaate hain
💡Tip: यह भी एक preview feature है (Java 21)। इसका main idea समझना important है: concurrent sub-tasks को एक unit की तरह treat करना, ताकि कोई thread "leak" न हो जाए या orphan न रह जाए।

Java 17 में ZGC (Z Garbage Collector) experimental था, low-latency के लिए design किया गया (pause times consistently 10ms से कम) — लेकिन यह "generational" नहीं था, मतलब young और old objects को अलग treat नहीं करता था, जिससे कुछ workloads में efficiency कम हो जाती थी।

Java 21 में ZGC "generational" बन गया (दूसरे modern GCs जैसे G1 की तरह) — young generation (जहाँ ज़्यादातर objects जल्दी "die" हो जाते हैं) को ज़्यादा बार और efficiently collect करता है, old generation को कम बार। इससे throughput और memory efficiency दोनों बेहतर हुए, बिना low-latency guarantee खोए।

GCFocus
G1 (default)Balanced throughput + pause time, general-purpose
ZGC (Java 17)Ultra-low pause time, non-generational
Generational ZGC (Java 21)Ultra-low pause time + better throughput (generational)

कभी-कभी एक variable declare करना पड़ता है जिसका use ही नहीं करना (जैसे catch (Exception e) में अगर e use ही नहीं हो रहा, या record pattern में सिर्फ कुछ fields चाहिए बाकी नहीं)। Java 21 preview में underscore (_) को "unnamed variable" के रूप में use कर सकते हो — बताता है कि यह value जान-बूझकर ignore की जा रही है।

Record patterns में भी काम आता है — जैसे अगर Point(int x, int y) से सिर्फ x चाहिए, तो case Point(int x, var _) लिख सकते हो, मतलब y को explicitly "मुझे इसकी परवाह नहीं" बता सकते हो, code पढ़ने वाले को clarity मिलती है।

// catch block mein unused exception:
try {
  riskyOperation();
} catch (IOException _) {
  System.out.println("Kuch to gaya galat, details ki zaroorat nahi");
}

// Record pattern mein sirf zaroori field:
if (obj instanceof Point(int x, var _)) {
  System.out.println("x = " + x + " (y ignore kiya)");
}