🧵
Java 21

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
⚡ Quick Recap
  • Java 21 = LTS release (Sept 2023)
  • Virtual Threads = lakhon lightweight threads
  • Sequenced Collections = getFirst/getLast/reversed
  • Record Patterns = records destructure karna
  • Pattern Matching for switch = ab stable hai
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.

Interview trap: virtual threads ke saath ThreadLocal use karna careful rehna chahiye — agar lakhon virtual threads har ek apna ThreadLocal state rakhein, memory usage bahut badh sakta hai. Isiliye Java 21 ne "Scoped Values" bhi introduce kiya (preview), jo iska ek lighter alternative hai.

  • Memory mein kilobytes, OS thread ke ~1MB se kaafi kam
  • Ek JVM mein lakhon virtual threads chal sakte hain
  • Block hone par carrier thread automatically free ho jaata hai
  • Existing blocking code bina change kiye kaam karta hai
// 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).

Real-world use-case: jab kisi list ka "most recent" ya "oldest" item chahiye ho (jaise ek activity feed ya undo history), pehle manual index math karna padta tha — ab getLast()/getFirst() se seedha aur readable code milta hai.

  • getFirst() / getLast() — pehla / aakhri element nikalo
  • addFirst() / addLast() — shuru / end mein add karo
  • removeFirst() / removeLast() — shuru / end se hatao
  • reversed() — reversed view, 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.

Interview mein record patterns ko sealed classes ke saath combine karke poocha ja sakta hai — dono milkar functional languages jaisa "algebraic data type" pattern dete hain, jaha data ki shape aur usko process karne ka logic dono explicit aur type-safe hote hain.

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 -> ....

Guard conditions ka order matter karta hai — switch upar se neeche cases check karta hai, isliye specific guard wale cases (jaise when i > 0) ko generic case se pehle rakhna zaroori hai, warna generic case pehle match ho jaayega.

  • Type patterns — case Integer i -> ...
  • Record patterns — case Point(int x, int y) -> ...
  • Guard conditions — case Integer i when i > 0 -> ...
  • case null -> — ab directly handle ho sakta hai
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.

Interview mein pucha ja sakta hai STR kya hai — ye ek built-in "template processor" hai (StringTemplate.Processor interface implement karta hai). Developers apna khud ka processor bhi bana sakte hain, jaise ek jo automatically HTML-escape kare XSS se bachne ke liye.

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.

Ye traditional try-catch-finally ke saath manual thread management se bahut zyada readable hai — purane tareeke mein agar ek task fail ho jaaye, doosre tasks ko manually cancel karna padta tha aur thread leaks ka risk rehta tha, StructuredTaskScope ye sab automatically handle kar leta 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.

Interview mein pucha ja sakta hai ki generational ZGC kyun zaroori tha jab G1 already generational tha — jawaab: G1 generational hone ke bawajood ZGC jaisa ultra-low pause time nahi de sakta tha; Generational ZGC dono cheezein — low pause time AND generational efficiency — ek saath deta hai.

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.

Ye feature khaas taur par bade codebases mein useful hai jaha static analysis tools (jaise SonarQube) "unused variable" warnings dete hain — underscore (_) use karke tum explicitly bata dete ho ki variable jaan-boojh kar ignore ho raha hai, warning bhi clear ho jaati 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)");
}