Java 21 LTS Enhancements
Java 21 (LTS, September 2023) is the 2026 enterprise baseline — the release staff engineers standardize on when greenfielding services, planning migrations off Java 8/11, and ev…
Introduction
Java 21 (LTS, September 2023) is the 2026 enterprise baseline — the release staff engineers standardize on when greenfielding services, planning migrations off Java 8/11, and evaluating Spring Boot 3.2+. Three features alone change how you architect high-throughput systems: virtual threads (Project Loom), sequenced collections, and record patterns.
Virtual threads decouple task concurrency from OS thread count — a Spring Boot payment API can handle 50,000 concurrent I/O-bound requests without reactive programming complexity. Sequenced collections finally give List, Set, and Map a defined encounter order with first/last/reversed operations — no more guessing HashMap iteration order. Record patterns complete the pattern-matching story — destructure Payment(String id, Money amount) in one switch expression instead of verbose getter chains.
This lesson explains why Java 21 LTS matters for production, how each feature works internally, and how Netflix-style services adopt them with Spring Boot — not release-note trivia.
Business problem
Staying on pre-21 Java costs measurable engineering and operations budget:
- Thread pool exhaustion: Platform-thread pools capped at 200–500 threads — payment gateway timeouts during traffic spikes because every blocked HTTP call holds an OS thread.
- Reactive complexity tax: Teams adopt WebFlux for concurrency — then struggle with debugging, stack traces, and hiring.
- Fragile collection ordering: Code depends on LinkedHashMap insertion order without declaring it — breaks when someone swaps to HashMap.
- Boilerplate domain handling: switch on sealed types with manual instanceof + casts — 40 lines where record patterns need 8.
- LTS security debt: Java 8/11 EOL patches ending — compliance failures on PCI/SOC2 audits.
Why this topic exists
Java 21 LTS addresses decade-old platform limitations:
- Project Loom: OS threads are expensive (~1MB stack). Blocking I/O on platform threads limits throughput. Virtual threads make blocking code scale like async — without rewriting to reactive.
- Sequenced Collections (JEP 431): Developers assumed list order and "first element" for 30 years — but List interface never guaranteed reversal or uniform first/last API until Java 21.
- Record Patterns (JEP 440): Records reduced boilerplate for data carriers; record patterns enable deconstruction in switch — completing pattern matching for domain modeling.
- LTS cadence: 21 is supported until at least 2031 (Oracle/Temurin) — safe multi-year investment for enterprises.
Core concepts
Three Java 21 pillars — definitions:
- Virtual threads: Lightweight threads scheduled by JVM, not OS. Created with
Thread.ofVirtual().start(runnable)orExecutors.newVirtualThreadPerTaskExecutor(). Blocking call parks virtual thread, frees carrier platform thread. Millions of virtual threads, kilobytes each. - Sequenced collections:
SequencedCollection,SequencedSet,SequencedMap— uniform API:getFirst(),getLast(),reversed(). LinkedHashSet, LinkedHashMap, ArrayList implement sequenced interfaces. - Record patterns: Deconstruct records in switch/if:
case Payment(String id, Money amt) -> .... Works with sealed hierarchies for exhaustive matching. Nested patterns supported. - Also in 21 LTS: Pattern matching for switch (finalized), generational ZGC, string templates (preview), unnamed patterns (preview in later versions).
Internal architecture
Virtual thread architecture — carrier model:
┌──────────────────── JVM ────────────────────────────────────────────┐│ Virtual Thread 1 ──blocking HTTP──▶ parked (unmounted from carrier) ││ Virtual Thread 2 ──running────────▶ on Carrier (platform thread) ││ Virtual Thread 3 ──blocking DB────▶ parked ││ ... millions of virtual threads ... │├────────────────────────────────────────────────────────────────────────┤│ ForkJoinPool "carrier" pool — limited platform threads (e.g. # CPUs) ││ When virtual thread blocks → carrier freed for another virtual thread ││ When I/O completes → virtual thread rescheduled on any free carrier │└────────────────────────────────────────────────────────────────────────┘Spring Boot 3.2+ virtual threads:spring.threads.virtual.enabled=true→ Tomcat/Jetty uses virtual thread executor per requestSequenced collection hierarchy:Collection└── SequencedCollection → getFirst(), getLast(), reversed()Set└── SequencedSetMap└── SequencedMap → firstEntry(), lastEntry(), sequencedKeySet()Record pattern flow:switch (event) {case PaymentCompleted(String id, Money amt) -> process(id, amt);case PaymentFailed(String id, String reason) -> alert(id, reason);}
Each diagram below is followed by a node-by-node breakdown — what every box means, why it exists, and the concrete benefit you get in a payment or API service.
Diagram 1 — Platform threads vs virtual threads
- 1 OS thread (~1MB stack): A platform thread is a wrapper around an operating-system thread. The JVM allocates roughly 1 MB of stack space per thread. On a 8 GB container, you can sustain only a few thousand platform threads before memory pressure — this is why traditional Spring apps cap thread pools at 200–500.
- 1 task (1:1 mapping): In the classic model, one HTTP request = one platform thread for its entire lifetime. While that thread waits on a database or downstream API, it is blocked but still consumes the OS thread. Benefit of the old model: simple mental model. Cost: throughput collapses when concurrency exceeds pool size.
- 1M virtual (~KB each): A virtual thread is a JVM-managed continuation — kilobytes of memory, not megabytes. You can have millions of in-flight payment authorizations because each blocked call parks the virtual thread instead of hoarding an OS thread. Benefit: thread-per-request semantics without thread-pool tuning or request queueing.
- N carriers (CPU-bound pool): Carriers are ordinary platform threads (typically one per CPU core) that run virtual thread bytecode. Thousands of virtual threads time-share a small carrier pool. Benefit: CPU stays busy while I/O waits happen off-carrier — the same goal as reactive programming, but with blocking code style.
Diagram 2 — Virtual thread lifecycle
- start() — Mounted: When you call
Thread.ofVirtual().start()or submit tonewVirtualThreadPerTaskExecutor(), the JVM mounts the virtual thread onto a free carrier. Your controller method begins executing real bytecode on a real CPU core. - block I/O — Parked: When code hits a blocking call (JDBC query, HTTP client,
Thread.sleep), the JVM unmounts the virtual thread and frees the carrier for another virtual thread. Benefit: 10,000 concurrent DB waits do not need 10,000 OS threads — this is the core scalability win. - I/O done — Resumed: When the socket or database responds, the JVM schedules the virtual thread onto any available carrier — not necessarily the original one. Execution continues exactly where it left off, as if the block never tied up a platform thread. Benefit: no callback hell; stack traces stay readable.
- complete — Unmounted: When the runnable finishes (or throws), the virtual thread is destroyed. No pool to return to, no leak if you forgot to release — creation cost is microseconds. Benefit: Spring Boot can dispatch one virtual thread per request with zero pool configuration.
Diagram 3 — Sequenced Collection API
- getFirst(): Returns the first element in encounter order — the order you meet elements when iterating. Works on
ArrayList,LinkedHashSetviews, andSequencedMapkey sets uniformly. Benefit: no morelist.get(0)(fails on empty with index error) or assuming Deque — one API for “earliest settlement batch.” - getLast(): Returns the last element in encounter order without
list.get(list.size() - 1). On aLinkedHashMaptracking daily totals,lastEntry()is yesterday’s closing balance. Benefit: clearer intent in ledger and audit code; fewer off-by-one bugs. - reversed(): Returns a view of the collection in reverse encounter order — for
LinkedHashMapthis is O(1) to create; iteration walks backwards. Benefit: process settlement batches from newest to oldest without copying into a new list or manual index math. - addFirst() / addLast(): Insert at the front or back of the sequence in O(1) on deque-backed structures. Benefit: priority replay queues and sliding audit windows without separate
Dequetypes — the type system declares “order matters here.”
Diagram 4 — Record pattern deconstruction
- switch(event) — Sealed type: You switch on a
sealed interface PaymentEventthatpermitsonly known implementations. The compiler knows the full set of possibilities. Benefit: domain events are a closed algebra — no surprise subclasses at runtime from another JAR. - case Payment(...) — Bind fields: A record pattern deconstructs the record in one step:
case PaymentCompleted(String id, double amt)bindsidandamtdirectly — noinstanceof, no cast, nogetPaymentId(). Benefit: event handlers shrink from 15 lines to 3; fewer null and cast bugs in Kafka consumers. - nested Money — Deep match: Patterns nest:
case OrderPaid(String id, Money(var amount, var currency))pulls fields from inner records in the same case. Benefit: express complex JSON/event shapes in one readable branch — ideal for payment amount + currency validation. - exhaustive — Compiler check: If
permitslists exactly two records and yourswitchhandles both, nodefaultis required. Add a third event type → compile error until you handle it. Benefit: refactoring safety when product addsPaymentPending— you cannot forget to update the handler.
How the three features work together
Virtual threads fix concurrency — your payment API accepts more simultaneous requests without reactive rewrites. Sequenced collections fix data ordering contracts — daily settlement maps and batch logs behave predictably in code reviews. Record patterns fix domain event handling — Kafka/Spring event listeners stay correct as event types evolve. Together they define why Java 21 is the LTS baseline for new Spring Boot services in 2026.
Code walkthrough
Java 21 in a payment processing service — virtual threads, sequenced collections, record patterns:
- SequencedMap (line 18): LinkedHashMap implements SequencedMap — insertion-ordered first/last/reversed without index math.
- Record patterns (line 12):
case PaymentCompleted(String id, double amt)binds fields directly — no getters or casts. - Sealed + switch: Compiler verifies exhaustiveness — new event type without case = compile error.
- Virtual threads (line 48): 10,000 concurrent tasks with ~10 platform carrier threads — impossible with thread-per-request platform model.
import java.util.*;import java.util.concurrent.*;// ── RECORD + SEALED HIERARCHY for pattern matching ──sealed interface PaymentEvent permits PaymentCompleted, PaymentFailed {}record PaymentCompleted(String paymentId, double amount) implements PaymentEvent {}record PaymentFailed(String paymentId, String reason) implements PaymentEvent {}class EventProcessor {// RECORD PATTERNS — destructure in switch (Java 21)static String handle(PaymentEvent event) {return switch (event) {case PaymentCompleted(String id, double amt) ->"Settled " + id + " amount=" + amt;case PaymentFailed(String id, String reason) ->"Failed " + id + ": " + reason;}; // exhaustive — sealed permits only these two}}class Java21Demo {public static void main(String[] args) throws Exception {// ── SEQUENCED COLLECTIONS ──SequencedMap<String, Double> dailyTotals = new LinkedHashMap<>();dailyTotals.put("MON", 1000.0);dailyTotals.put("TUE", 1500.0);dailyTotals.put("WED", 1200.0);System.out.println("First day: " + dailyTotals.firstEntry());System.out.println("Last day: " + dailyTotals.lastEntry());System.out.print("Reversed: ");dailyTotals.reversed().forEach((k, v) -> System.out.print(k + "=" + v + " "));SequencedCollection<String> queue = new ArrayList<>();queue.add("batch-1");queue.add("batch-2");System.out.println("\nFirst batch: " + queue.getFirst());System.out.println("Last batch: " + queue.getLast());// ── RECORD PATTERNS ──PaymentEvent ok = new PaymentCompleted("PAY-100", 250.00);PaymentEvent fail = new PaymentFailed("PAY-101", "insufficient funds");System.out.println("\n" + EventProcessor.handle(ok));System.out.println(EventProcessor.handle(fail));// ── VIRTUAL THREADS ──long start = System.currentTimeMillis();try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {List<Future<String>> futures = new ArrayList<>();for (int i = 0; i < 10_000; i++) {int id = i;futures.add(executor.submit(() -> {Thread.sleep(10); // simulate I/O — parks virtual threadreturn "payment-" + id;}));}System.out.println("\nVirtual threads completed: " + futures.size()+ " in " + (System.currentTimeMillis() - start) + "ms");System.out.println("Carrier thread count ~" +Thread.getAllStackTraces().keySet().stream().filter(t -> !t.isVirtual()).count());}}}/** Expected output (approximate):* First day: MON=1000.0* Last day: WED=1200.0* Reversed: WED=1200.0 TUE=1500.0 MON=1000.0* First batch: batch-1* Last batch: batch-2* Settled PAY-100 amount=250.0* Failed PAY-101: insufficient funds* Virtual threads completed: 10000 in ~50-200ms* Carrier thread count ~10-20 (not 10000!)*/
Production example
Spring Boot 3.2+ — Java 21 in production configuration:
- spring.threads.virtual.enabled=true — one flag enables virtual threads for Tomcat, @Async, @Scheduled (Boot 3.2+).
- Blocking JPA/RestTemplate — safe again at scale; no WebFlux rewrite required for I/O-bound services.
- Record patterns in handlers — event-driven architecture with typed deconstruction — Kafka consumers map JSON to records then switch.
- LinkedHashMap as SequencedMap — time-series aggregation with first/last/reversed for reporting.
# application.yml — enable virtual threads (Spring Boot 3.2+)spring:threads:virtual:enabled: true# DockerfileFROM eclipse-temurin:21-jre-alpineCOPY build/libs/payments.jar /app.jarENTRYPOINT ["java", "-XX:+UseZGC", "-jar", "/app.jar"]// Controller — each request on virtual thread automatically@RestControllerpublic class PaymentController {private final PaymentService service;@PostMapping("/payments")public PaymentDto create(@RequestBody PaymentRequest req) {return service.process(req); // blocking JPA + HTTP OK on virtual thread}}// Domain — record patterns in event handler@Componentpublic class PaymentEventHandler {public void on(PaymentEvent event) {switch (event) {case PaymentCompleted(String id, Money amount) ->ledgerService.credit(id, amount);case PaymentFailed(String id, String reason) ->alertService.notify(id, reason);}}}// Sequenced collection — ordered settlement batchSequencedMap<LocalDate, BigDecimal> settlements = new LinkedHashMap<>();settlements.put(today, total);BigDecimal yesterday = settlements.reversed().sequencedValues().getFirst();
Enterprise case study
Netflix / industry adoption pattern: Early adopters on Java 21 virtual threads report 2–5× throughput improvement on I/O-bound microservices without reactive rewrites. A fintech payment router (similar architecture to Stripe's internal routing) migrated from platform thread pool (max 400) to virtual threads — peak concurrent requests rose from 400 to 25,000 with same CPU/memory footprint. GC pauses unchanged (ZGC). Key constraint discovered: pinning — synchronized blocks inside JDBC drivers or native code can pin virtual thread to carrier; monitor with JFR event jdk.VirtualThreadPinned. Sequenced collections adopted in ledger reconciliation — reversed iteration over daily batches without manual index.
- Before: 400 platform threads, queueing at gateway, p99 latency spikes under load.
- After: Virtual threads per request, 25k concurrent, p99 stable.
- Pitfall found: synchronized in legacy library pinned carriers — upgraded driver, enabled JFR pinning alerts.
- Record patterns: Event handler code reduced 60% — fewer bugs in field extraction.
Performance considerations
Java 21 performance guidance:
- Virtual threads for I/O-bound: HTTP APIs, DB calls, message polling — massive concurrency win. CPU-bound work still needs platform threads or limited pool.
- Don't pool virtual threads: Create per task — pooling is anti-pattern; creation cost is microseconds.
- Pinning kills scalability: synchronized + native JNI in hot path — use ReentrantLock or update library. JFR: VirtualThreadPinned event.
- Generational ZGC (Java 21): Default low-latency GC — pair with virtual threads for sub-10ms p99 on payment APIs.
- Sequenced reversed(): O(1) view for LinkedHashMap; O(n) copy for ArrayList — know implementation.
Security considerations
Java 21 security and correctness:
- ThreadLocal with virtual threads: Millions of virtual threads × ThreadLocal = memory leak risk. Use ScopedValue (preview/JEP 446) or explicit request context.
- Exhaustion attacks: Virtual threads cheap to create — rate limit at gateway; unbounded executor.submit loop is DoS vector.
- Record pattern exhaustiveness: Sealed types + switch prevent missing new event types — security events not silently dropped.
- LTS patching: Java 21 receives CVE fixes through 2031 — mandate Temurin/Corretto 21 in supply chain policy.
Scalability considerations
Scaling with Java 21 features:
- Virtual threads + K8s: Scale on latency/CPU not thread count — fewer pods needed for same concurrent request capacity.
- Carrier pool sizing: Default = CPU count; increase only for CPU work mixed with I/O on same executor.
- Structured concurrency (JEP 453 preview): StructuredTaskScope — parent virtual thread waits for children; failure cancels siblings.
- Sequenced collections in streaming: reversed() for stack-like processing of ordered batches without copying.
Production challenges
Real migration challenges from Java 8/11 to 21:
- javax → jakarta: Spring Boot 3.x requires Jakarta EE — not Java 21 specific but blocks migration path.
- ThreadLocal abuse: Request context in ThreadLocal works but scales poorly with virtual threads — refactor to ScopedValue.
- Pinning in drivers: Older JDBC drivers synchronize internally — profile before declaring virtual thread victory.
- Thread pool assumptions: Code tuning pool size for platform threads — remove pools for virtual thread workloads.
- Monitoring: Metrics assuming thread count = load — virtual thread count differs; monitor request latency and carrier utilization.
Common mistakes
- Using virtual threads for CPU-bound computation — no benefit; use platform thread pool sized to CPUs.
- Pooling virtual threads — unnecessary; create with newVirtualThreadPerTaskExecutor().
- Assuming HashMap is sequenced — use LinkedHashMap or SequencedMap explicitly when order matters.
- Record patterns without sealed hierarchy — compiler cannot enforce exhaustiveness.
- Ignoring pinning warnings in JFR — silent carrier pool exhaustion under load.
Debugging guide
Debug Java 21 production issues:
- Virtual thread dump:
jcmd <pid> Thread.dump_to_file— shows virtual vs carrier threads. - Pinning detection: JFR enable jdk.VirtualThreadPinned — stack trace shows synchronized/native pin site.
- Record pattern MatchException: Null component in record — guard with null checks before switch or use guarded patterns.
- SequencedCollection NoSuchElementException: getFirst() on empty collection — use isEmpty() check.
# Enable virtual thread JFR eventsjfr start name=vt settings=default# ... load test ...jfr print --events jdk.VirtualThreadPinned,jdk.VirtualThreadSubmit recording.jfr# Verify Java 21 in productionjava -version # openjdk version "21.0.x"# Spring Boot virtual threads active# logging.level.org.apache.tomcat.util.threads=DEBUG
Best practices
- Standardize new services on Java 21 LTS + Spring Boot 3.2+ with virtual threads for I/O-bound APIs.
- Enable
spring.threads.virtual.enabled=true— measure before/after p99 and throughput. - Use
SequencedMap/LinkedHashMapwhen encounter order is part of domain semantics. - Model events as sealed interface + records — record patterns in switch for handlers.
- Monitor JFR VirtualThreadPinned — fix pinning before peak traffic.
- Replace ThreadLocal request context with ScopedValue as libraries mature.
- Pair Java 21 with ZGC:
-XX:+UseZGCfor payment latency SLOs.
Anti-patterns
- Rewriting to WebFlux when virtual threads solve the same I/O concurrency problem — reactive complexity tax.
- Fixed thread pool of 200 platform threads in Java 21 service — obsolete for I/O-bound work.
- get(0) and get(list.size()-1) instead of getFirst()/getLast() — fails for non-RandomAccess or empty lists inconsistently.
- Non-sealed records with switch — missing cases compile but fail at runtime with MatchException.
- Creating millions of virtual threads in tight loop without backpressure — memory pressure from task objects.
Staff engineer notes
- Staff engineers recommend Java 21 LTS for all new work in 2026 — virtual threads change the default concurrency model from "pool" to "per-task."
- Virtual threads are not "free" — pinning, ThreadLocal, and CPU-bound sections still need engineering discipline.
- Sequenced collections are API hygiene — declare order intent in type system, not comments.
- Record patterns + sealed types = algebraic data types in Java — use for domain events, API responses, and state machines.
- Migration ADR template: Java 8 → 21 includes javax/jakarta, virtual thread pinning audit, ZGC flags, and LTS vendor choice.
Interview questions
Interview preparation
15 questions grouped by difficulty — expand any card for a model answer and follow-up probe.
Beginner
1What are virtual threads in Java 21?
BeginnerModel answer
Lightweight threads managed by JVM, not OS.
Many virtual threads multiplex onto few platform thread carriers.
Blocking I/O parks virtual thread without holding carrier.
newVirtualThreadPerTaskExecutor().
Ideal for I/O-bound concurrency.
Follow-up probe
Platform vs virtual thread?
2What is Java 21 LTS and why does it matter?
BeginnerModel answer
Long-Term Support release (September 2023).
Vendor patches (Temurin, Corretto) until ~2031.
Enterprise baseline with virtual threads, record patterns, sequenced collections, generational ZGC.
).
Follow-up probe
Java 21 vs 17 LTS?
3What are sequenced collections?
BeginnerModel answer
Java 21 interfaces SequencedCollection, SequencedSet, SequencedMap with uniform encounter-order API: getFirst(), getLast(), reversed(), addFirst(), addLast().
LinkedHashMap, LinkedHashSet, ArrayList implement them.
Explicit order contract vs implicit assumptions.
Follow-up probe
Is HashMap sequenced?
4Explain record patterns with an example.
BeginnerModel answer
Deconstruct record fields in switch/if: case PaymentCompleted(String id, double amt) -> process(id, amt).
Binds components to variables.
Works with sealed hierarchies for exhaustiveness.
Replaces instanceof + cast + getters.
Follow-up probe
Nested record patterns?
5When should you NOT use virtual threads?
BeginnerModel answer
- CPU-bound computation
- virtual threads don't add CPU capacity; use platform threads sized to cores. Code with heavy synchronized blocks or JNI that pins carriers. Deep ThreadLocal usage without ScopedValue migration plan.
Follow-up probe
What is pinning?
Intermediate
6How do you enable virtual threads in Spring Boot?
IntermediateModel answer
yml.
Tomcat/Jetty dispatches each request on virtual thread.
@Async and @Scheduled can use virtual threads.
Requires Java 21.
Follow-up probe
Works with @Transactional?
7What is virtual thread pinning?
IntermediateModel answer
- When virtual thread blocks on synchronized or native method, it may pin to carrier platform thread
- carrier cannot serve other virtual threads during block. Reduces scalability. Fix: replace synchronized with ReentrantLock, update libraries, monitor JFR VirtualThreadPinned.
Follow-up probe
How detect in production?
8Virtual threads vs reactive (WebFlux)?
IntermediateModel answer
Virtual threads: blocking code style, thread-per-request model scales with I/O.
WebFlux: non-blocking stack end-to-end, backpressure, steeper learning curve.
For typical Spring MVC + JPA service, virtual threads win simplicity.
WebFlux when entire stack non-blocking or backpressure critical.
Follow-up probe
Can mix both?
9Difference between getFirst() and get(0)?
IntermediateModel answer
- getFirst() on SequencedCollection
- works uniformly on List, Deque views; throws NoSuchElementException if empty. get(0) List-specific, IndexOutOfBoundsException if empty. getFirst() expresses intent on any sequenced collection including reversed views.
Follow-up probe
reversed() mutates?
10How do sealed types + record patterns work together?
IntermediateModel answer
- sealed interface PaymentEvent permits PaymentCompleted, PaymentFailed. Records implement permits. switch(event) with record patterns
- compiler checks all permitted subtypes handled. Add new permitted type without case = compile error. Algebraic data types in Java.
Follow-up probe
default case needed?
Advanced
11Design payment service concurrency on Java 21.
AdvancedModel answer
2, virtual threads enabled.
Blocking JPA + RestTemplate on virtual threads.
CPU-bound fraud scoring on separate ExecutorService with platform threads = CPUs.
Sealed PaymentEvent records + switch handlers.
ZGC.
JFR pinning monitoring.
Rate limit at gateway.
Follow-up probe
Connection pool sizing?
12Migrate 200 microservices from Java 11 to 21 — strategy?
AdvancedModel answer
x, javax→jakarta audit.
Phase 2: pilot 5 I/O-bound services with virtual threads + JFR pinning check.
Phase 3: enable virtual threads per team with rollback flag.
Phase 4: adopt record patterns in new event code.
Track GC, latency, thread metrics.
LTS vendor pinned.
Follow-up probe
Biggest blocker?
13Explain carrier thread pool internals.
AdvancedModel answer
ForkJoinPool schedules virtual threads.
Carrier = platform thread running virtual thread bytecode.
On blocking park, virtual thread unmounts, carrier free.
On wakeup, reschedule on any available carrier.
availableProcessors().
Follow-up probe
Tune carrier count?
14SequencedMap vs LinkedHashMap — relationship?
AdvancedModel answer
LinkedHashMap implements SequencedMap since Java 21.
SequencedMap is the interface declaring firstEntry(), lastEntry(), reversed(), pollFirstEntry().
Use SequencedMap as type for API expressing order requirement; LinkedHashMap as default impl.
Follow-up probe
TreeMap sequenced?
15Record patterns vs traditional visitor pattern?
AdvancedModel answer
Visitor: external double-dispatch, boilerplate per type.
Record patterns: built-in deconstruction in switch, no visitor interface hierarchy.
Sealed + records + switch = concise exhaustive handling.
Visitor still useful when behavior varies by external context not event type.
Follow-up probe
Performance difference?
Hands-on exercise
Lab: Java 21 features — run playground, then extend:
- Run demo — observe sequenced map reversed order, record pattern switch output, virtual thread completion count.
- Add new record PaymentPending(String id) to sealed hierarchy — observe compile error until switch updated.
- Use sequencedValues().getFirst() on reversed map — print latest day's total.
- Spawn 1000 virtual threads with Thread.sleep — compare to platform thread pool (if playground supports).
- Bonus: write application.yml snippet enabling Spring virtual threads.
JavaJava 21 LTS Enhancements
Press Run to execute. Real javac + JVM via remote API. Falls back to simulator on network failure.Architecture trade-offs
- Virtual threads vs WebFlux: Virtual threads win blocking-code simplicity; WebFlux wins end-to-end reactive backpressure.
- Java 21 vs 17 LTS: 21 wins virtual threads + record patterns; 17 wins if vendor/libs not ready for 21 yet.
- LinkedHashMap vs TreeMap: LinkedHashMap insertion-ordered sequenced; TreeMap sorted by key — different order semantics.
- Record patterns vs if-instanceof: Patterns win readability and exhaustiveness; instanceof fine for one-off checks.
Summary
Java 21 LTS is a platform shift, not a feature dump — virtual threads redefine concurrency for Spring Boot services, sequenced collections formalize ordering contracts, and record patterns complete modern domain modeling. You can now evaluate migration from Java 8/11, enable virtual threads safely, and apply record patterns to payment event handling. Next: structured concurrency and ScopedValue as you deepen Java 21 adoption.
Key takeaways
- Java 21 LTS is the 2026 enterprise baseline — supported until ~2031 with security patches.
- Virtual threads: millions of lightweight threads for I/O-bound work — spring.threads.virtual.enabled=true.
- Sequenced collections: getFirst(), getLast(), reversed() — explicit encounter order API.
- Record patterns: deconstruct records in switch — sealed types enable exhaustiveness.
- Watch pinning (synchronized/native) — JFR VirtualThreadPinned; CPU-bound work stays on platform threads.