🧵
Java 8+

Java 21 Features

Virtual Threads & Record Patterns
💡 Java 21 ek bade restaurant jaisa hai jisne apna poora staffing model badal diya — pehle har table ke liye ek dedicated waiter (heavy OS thread) chahiye hota tha, ab ek chhota flexible staff (virtual threads) lakhon tables ko efficiently handle kar leta hai, kyunki wo kisi table ke "wait" karte waqt doosri table sambhal leta hai.

Java 21 (September 2023) teesri major LTS (Long-Term Support) release hai Java 17 ke baad — isme Project Loom ka sabse bada result, Virtual Threads, stable feature ban kar aaya, jo Java mein concurrency likhne ka tareeka fundamentally badal deta hai.

Sabse bade highlights: Virtual Threads (lakhon lightweight threads), Sequenced Collections (getFirst/getLast/reversed jaise common methods), Record Patterns (records ko destructure karna), aur Pattern Matching for switch (finally stable, guard conditions ke saath).

Kuch features abhi bhi preview mein hain (String Templates, Structured Concurrency, Unnamed Variables) — matlab syntax future versions mein thoda badal sakta hai, lekin concept samajhna interview ke liye zaroori hai.

// 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 ek bade restaurant jaisa hai jisne apna poora staffing model badal diya — pehle har table ke liye ek dedicated waiter (heavy OS thread) chahiye hota tha, ab ek chhota flexible staff (virtual threads) lakhon tables ko efficiently handle kar leta hai, kyunki wo kisi table ke "wait" karte waqt doosri table sambhal leta hai.
1 / 6
Is page mein (8 subtopics)

Traditional Java thread ek OS thread ka direct wrapper hai — har thread banane mein memory (typically ~1MB stack) aur OS resources lagte hain, isliye ek JVM mein practically kuch hazaar threads hi ban paate hain bina system slow kiye. High-concurrency applications (jaise ek web server jo lakhon requests handle kare) is limit se takrate hain.

Virtual thread JVM ke andar hi manage hota hai (OS thread nahi) — bahut halka hai (kilobytes mein, MB nahi), aur ek JVM mein lakhon virtual threads ban sakte hain bina problem ke. Jab virtual thread block hota hai (jaise I/O wait), JVM us underlying OS thread ko free kar deta hai doosre virtual threads ke liye — matlab thoda sa OS threads (carrier threads) bahut saare virtual threads ko serve kar sakte hain.

Sabse achhi baat: code likhne ka tareeka bilkul same rehta hai — Thread.ofVirtual().start(...) ya Executors.newVirtualThreadPerTaskExecutor() use karo, baaki sab (synchronized, try-catch, normal blocking calls) waise hi kaam karta hai jaise normal thread mein karta tha. Koi naya async/reactive syntax seekhna nahi padta.

// 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 kaam (jaise heavy calculations) ke liye faayda nahi dete — inka fayda tabhi hai jab thread I/O (network, file, database) ka wait kare. CPU-bound tasks ke liye traditional platform threads/parallel streams hi behtar hain.

Purane Java mein List (jisme order hota hai) aur LinkedHashSet/LinkedHashMap (jinme bhi predictable order hota hai) ke paas "first element" ya "last element" nikalne ka koi common, consistent tareeka nahi tha — List.get(0) vs List.get(size()-1), ya alag-alag collections mein alag methods.

Java 21 ne teen naye interfaces diye: SequencedCollection, SequencedSet, SequencedMap — jo List, LinkedHashSet, LinkedHashMap jaise ordered collections ko common methods dete hain: getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), aur reversed() (jo collection ko reversed view mein dekhta hai, bina copy banaye).

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() ek "view" return karta hai (naya object copy nahi banata) — matlab agar original collection change ho, reversed view bhi automatically update ho jaata hai.

Java 17 mein records aaye the data classes ke liye, aur Java 16+ mein instanceof pattern matching aayi thi. Java 21 ne inko combine kar diya — ab record ke andar ke fields ko seedha "destructure" (nikaal) kar sakte ho ek instanceof ya switch check mein, bina record.field() call kiye.

Jaise agar record Point(int x, int y) hai, to if (obj instanceof Point(int x, int y)) likhne se, agar obj ek Point hai, to x aur y directly available ho jaate hain — record ke andar record bhi ho sakta hai (nested destructuring), jo deeply nested data structures ke liye bahut clean code deta hai.

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 mein ye preview feature tha, Java 21 mein finally stable/final ho gaya. Ab switch expression/statement mein type-based branching, record destructuring, aur guard conditions (when clause) sab ek saath use kar sakte ho.

null bhi ab switch mein directly handle ho sakta hai (case null ->) — pehle switch null par NullPointerException deta tha, isliye alag se null check karna padta tha. Guard condition (when) se ek type-match ke andar bhi extra condition laga sakte ho, jaise 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 ke saath combine karo to compiler khud confirm kar sakta hai ki switch ke saare possible cases cover ho gaye ("exhaustiveness") — default case ki bhi zaroorat nahi padti.

String concatenation (+ se jodna) ya String.format() dono thode clunky hain lambi strings ke liye. String Templates ek naya syntax lati hain — STR."Hello \{name}!" jaisa — jisme \{...} ke andar directly Java expressions daal sakte ho, aur wo automatically evaluate hoke result mein embed ho jaate hain.

Ye ek preview feature hai Java 21 mein (matlab --enable-preview flag ke saath compile/run karna padta hai, aur future versions mein change bhi ho sakta hai) — lekin ye dikhata hai Java string handling ko kaha le jaa raha hai: readable, type-safe, aur SQL-injection jaisi cheezein bhi easily prevent karne wale custom template processors (jaise SQL processor) ban sakte hain.

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: Ye preview feature hai — production code mein bina --enable-preview flag ke compile hi nahi hoga, aur syntax future JDK versions mein badal bhi sakta hai. Interview mein concept samajhna zaroori hai, syntax exactly yaad rakhna utna zaroori nahi.

Jab ek task ko complete karne ke liye multiple sub-tasks parallel mein chalane padte hain (jaise ek user profile fetch karne ke liye alag-alag services se data, price aur inventory), unhe manually manage karna (ek fail ho jaaye to baaki cancel karna, sabka wait karna, errors handle karna) error-prone hota hai.

StructuredTaskScope in sub-tasks ko ek "structured" tareeke se treat karta hai — parent task tab tak khatam nahi hota jab tak saare child tasks (jo usne start kiye) khatam na ho jaayein, aur agar ek child fail ho, poora group automatically cancel ho sakta hai (fail-fast). Ye virtual threads ke saath milkar concurrent code ko bahut safer aur readable banata hai.

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: Ye bhi ek preview feature hai (Java 21). Iska main idea samajhna important hai: concurrent sub-tasks ko ek unit ki tarah treat karna, taaki koi thread "leak" na ho jaaye ya orphan na reh jaaye.

Java 17 mein ZGC (Z Garbage Collector) experimental tha, low-latency ke liye design kiya gaya (pause times consistently 10ms se kam) — lekin ye "generational" nahi tha, matlab young aur old objects ko alag treat nahi karta tha, jisse kuch workloads mein efficiency kam ho jaati thi.

Java 21 mein ZGC "generational" ban gaya (dusre modern GCs jaise G1 ki tarah) — young generation (jaha zyadatar objects jaldi "die" ho jaate hain) ko zyada baar aur efficiently collect karta hai, old generation ko kam baar. Isse throughput aur memory efficiency dono behtar hue, bina low-latency guarantee khoye.

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)

Kabhi-kabhi ek variable declare karna padta hai jiska use hi nahi karna (jaise catch (Exception e) mein agar e use hi nahi ho raha, ya record pattern mein sirf kuch fields chahiye baaki nahi). Java 21 preview mein underscore (_) ko "unnamed variable" ke roop mein use kar sakte ho — batata hai ki ye value jaan-boojh kar ignore ki ja rahi hai.

Record patterns mein bhi kaam aata hai — jaise agar Point(int x, int y) se sirf x chahiye, to case Point(int x, var _) likh sakte ho, matlab y ko explicitly "mujhe iski parwaah nahi" bata sakte ho, code padhne wale ko clarity milti hai.

// 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)");
}