Java Fundamentals Tutorial 0/33 lessons ~6 min read Lesson 3

    Java LTS Versions: 8, 11, 17 & 21

    Enterprise Java is not one language — it is a timeline of LTS baselines that teams adopt every few years.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Enterprise Java is not one language — it is a timeline of LTS baselines that teams adopt every few years. Since Java 8 (March 2014), each LTS release bundles features that change how you write everyday code: functional APIs, HTTP clients, immutable data carriers, sealed domain models, and lightweight concurrency.

    This lesson maps the four LTS releases that matter for hiring, migration, and architecture reviews — Java 8, 11, 17, and 21 — with the flagship features you must explain in interviews and apply in production. Feature releases (9, 10, 12–16, 18–20) ship between LTS versions; enterprises standardize on LTS for vendor support and CVE patching.

    Business problem

    Version confusion costs real money:

    • Stuck on Java 8: No modules discipline, thread-per-request ceilings, verbose DTO boilerplate — and mounting security debt as vendors drop patches.
    • Partial upgrades: Teams on Java 11 still hand-roll HTTP with Apache HttpClient while missing records and sealed types from 17.
    • Interview failure: Senior loops expect you to compare Stream vs loop, explain var limits, and articulate why Java 21 virtual threads change Spring Boot defaults.
    • Migration paralysis: Without a version-by-version mental model, "upgrade to 21" feels like a big bang instead of incremental wins.

    Why this topic exists

    Oracle ships a feature release every six months, but enterprises anchor on LTS every two years because operations teams need predictable support windows — not chasing every preview flag.

    • Java 8 LTS (2014): Lambdas and Streams — functional style on the JVM without a new language.
    • Java 11 LTS (2018): First modular JDK, standard HTTP client, var (Java 10) adoption wave, removed standalone JRE.
    • Java 17 LTS (2021): Records, sealed classes — algebraic data modeling in Java.
    • Java 21 LTS (2023): Virtual threads, sequenced collections, pattern matching for switch — concurrency and domain modeling mature.

    Core concepts

    LTS release train — what each version added to your daily toolkit:

    • Java 8: Lambda expressions, method references, java.util.stream, Optional, java.time (JSR-310), default methods on interfaces.
    • Java 11: java.net.http.HttpClient (standard async HTTP), var local type inference, String helpers (isBlank, lines), TLS 1.3, ZGC experimental.
    • Java 17: record immutable data carriers, sealed classes/interfaces, pattern matching for instanceof, enhanced pseudo-random generators.
    • Java 21: Virtual threads (Project Loom), SequencedCollection/SequencedMap, pattern matching for switch, record patterns, generational ZGC default.

    Internal architecture

    Enterprise adoption timeline — most banks and e-commerce platforms traversed this path:

    text
    Java 8 (2014) Lambdas · Streams · java.time
    ▼ (feature releases 9–10: modules, var)
    Java 11 (2018) HttpClient · var · removed JRE
    ▼ (12–16: switch expr, records preview, text blocks)
    Java 17 (2021) Records · Sealed classes · instanceof patterns
    ▼ (18–20: pattern matching previews, virtual threads preview)
    Java 21 (2023) Virtual threads · Sequenced collections · switch patterns
    Java 25 LTS (2025) — next enterprise baseline (preview)

    Four LTS milestones — expand each diagram, then read the bullets explaining how the feature shows up in payment and ledger services.

    Java 8 — Lambdas & Streams

    Java 8 — functional data pipeline
    Collection<T>
    Source
    stream()
    Lazy pipeline
    filter/map
    Lambda ops
    collect
    Terminal
    Declarative bulk operations replace manual for-loops.
    • Lambdas: (Payment p) -> p.isSettled() — treat behavior as data; enables Stream API and CompletableFuture composition.
    • Streams: Lazy pipelines over collections — filter, map, reduce, parallel streams for CPU-bound batch settlement.
    • java.time: Instant, ZonedDateTime replace error-prone Date/Calendar — critical for cross-timezone ledgers.

    Java 11 — HTTP Client & var

    Java 11 — HttpClient request flow
    HttpClient
    Builder
    HttpRequest
    GET/POST
    sendAsync
    Non-blocking
    HttpResponse
    Typed body
    Replaces Apache HttpClient for outbound service calls.
    • HttpClient: Built-in HTTP/2, WebSocket support, async sendAsync — call payment gateways without third-party deps.
    • var: Local type inference — var payments = repo.findAll(); — less noise; cannot use for fields or method params. Shipped Java 10, widely adopted with 11 migrations.
    • String API: isBlank(), strip(), lines() — sanitize user input in APIs without Apache Commons.

    Java 17 — Records & Sealed Classes

    Java 17 — sealed domain hierarchy
    sealed Payment
    Closed set
    record Completed
    Immutable DTO
    record Failed
    Data carrier
    permits
    Compiler knows all
    Model payment events as a finite, typed algebra.
    • Records: record PaymentDto(String id, BigDecimal amount) — constructor, equals, hashCode, toString generated; immutable by default.
    • Sealed classes: sealed interface PaymentEvent permits Completed, Failed — compiler enforces closed hierarchy; pairs with pattern matching in 21.
    • instanceof patterns: if (o instanceof Payment p) — bind variable without cast — bridge to full switch patterns in 21.

    Java 21 — Virtual Threads & Pattern Matching

    Java 21 — virtual thread multiplexing
    10k requests
    Virtual threads
    park on I/O
    Unmount
    ~CPU carriers
    Platform pool
    resume
    Any carrier
    I/O-bound services scale without reactive rewrites.
    • Virtual threads: Executors.newVirtualThreadPerTaskExecutor() — millions of lightweight threads for blocking JPA/HTTP.
    • Sequenced collections: getFirst(), getLast(), reversed() — uniform encounter-order API on lists and maps.
    • Pattern matching: switch (event) { case Completed(String id, var amt) -> ... } — deconstruct records in one expression; exhaustive with sealed types.

    Code walkthrough

    One payment service — features from Java 8 through 21 (runs on Java 21, comments mark origin):

    • Java 8 streams (line 18): Declarative filter/map/sort — foundation for batch reporting.
    • Java 17 records (line 10): Immutable DTOs without Lombok — equals/hashCode correct for Map keys.
    • Java 11 var + HttpClient (line 36): Less boilerplate; standard HTTP without Apache dependency.
    • Java 21 switch patterns (line 26): Exhaustive handling when combined with sealed permits.
    java
    import java.net.URI;
    import java.net.http.*;
    import java.math.BigDecimal;
    import java.util.*;
    import java.util.concurrent.*;
    import java.util.stream.*;
    // ── JAVA 17: sealed + records ──
    sealed interface PaymentEvent permits Completed, Failed {}
    record Completed(String id, BigDecimal amount) implements PaymentEvent {}
    record Failed(String id, String reason) implements PaymentEvent {}
    class PaymentVersionsDemo {
    // ── JAVA 8: Stream pipeline ──
    static List<String> settledIds(List<Completed> payments) {
    return payments.stream()
    .filter(p -> p.amount().signum() > 0)
    .map(Completed::id)
    .sorted()
    .collect(Collectors.toList());
    }
    // ── JAVA 21: pattern matching for switch ──
    static String describe(PaymentEvent event) {
    return switch (event) {
    case Completed(String id, BigDecimal amt) ->
    "Settled " + id + " = " + amt;
    case Failed(String id, String reason) ->
    "Failed " + id + ": " + reason;
    };
    }
    // ── JAVA 11: HttpClient (sync) ──
    static String fetchStatus(String url) throws Exception {
    var client = HttpClient.newHttpClient(); // Java 11
    var request = HttpRequest.newBuilder(URI.create(url)).GET().build();
    var response = client.send(request, HttpResponse.BodyHandlers.ofString());
    return response.statusCode() + " " + response.body().substring(0, Math.min(40, response.body().length()));
    }
    public static void main(String[] args) throws Exception {
    // Java 11 — var for locals
    var events = List.of(
    new Completed("PAY-1", new BigDecimal("100.00")),
    new Failed("PAY-2", "timeout")
    );
    events.forEach(e -> System.out.println(describe(e)));
    var completed = events.stream()
    .filter(e -> e instanceof Completed)
    .map(e -> (Completed) e)
    .toList();
    System.out.println("Settled IDs: " + settledIds(completed));
    // Java 21 — sequenced map (daily totals)
    SequencedMap<String, BigDecimal> daily = new LinkedHashMap<>();
    daily.put("MON", new BigDecimal("1000"));
    daily.put("TUE", new BigDecimal("1500"));
    System.out.println("Latest day: " + daily.lastEntry());
    // Java 21 — virtual threads
    try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
    var future = exec.submit(() -> describe(events.get(0)));
    System.out.println("Virtual thread: " + future.get());
    }
    }
    }

    Production example

    Typical enterprise stack by LTS era:

    • Don't big-bang: Upgrade JDK first, adopt features incrementally per service.
    • Java 8 → 11: Remove JAXB/JAX-WS if used (removed from JDK), replace Nashorn, audit illegal reflective access.
    • Java 11 → 17: Spring Boot 3 + jakarta migration is often the gating item.
    • Java 17 → 21: Enable virtual threads on I/O-bound APIs; audit synchronized pinning in JDBC drivers.
    text
    # Java 8 era (legacy)
    - Spring Boot 2.x on Java 8
    - Lambdas + Streams in services; Optional for null-safe returns
    - RestTemplate + Apache HttpClient for outbound calls
    # Java 11 migration
    - Adopt HttpClient for new integrations
    - var in local variables (IDE-assisted)
    - Docker: eclipse-temurin:11-jre
    # Java 17 migration (Spring Boot 3.x)
    - record for API DTOs and Kafka events
    - sealed interface for closed event hierarchies
    - jakarta.* namespace (EE 9+)
    # Java 21 baseline (2026 greenfield)
    spring:
    threads:
    virtual:
    enabled: true
    # Dockerfile: eclipse-temurin:21-jre-alpine
    # -XX:+UseZGC

    Enterprise case study

    Retail bank payment platform migration (2018–2025): Started Java 8 / Spring Boot 1.x with thread pools capped at 200 concurrent authorizations. Java 11 migration replaced custom HttpURLConnection wrappers with HttpClient — cut outbound integration code 40%. Java 17 adoption introduced records for SWIFT message DTOs — eliminated Lombok version conflicts in CI. Java 21 pilot on the authorization microservice enabled virtual threads — peak concurrent authorizations rose from 200 to 18,000 on same pod CPU without WebFlux rewrite. Lesson: each LTS unlocked a different bottleneck — streams for code clarity, HttpClient for integration, records for data safety, virtual threads for concurrency.

    • Java 8 win: Stream-based reconciliation jobs — 30% less code, easier audits.
    • Java 11 win: Standard HTTP/2 to card networks — fewer connection timeouts.
    • Java 17 win: Records in event schemas — fewer deserialization bugs.
    • Java 21 win: Virtual threads — authorization p99 dropped 35% under Black Friday load.

    Performance considerations

    Version-specific performance notes:

    • Java 8 parallel streams: Use ForkJoinPool.commonPool() wisely — can starve other work; custom pool for batch jobs.
    • Java 11 HttpClient: Reuse single HttpClient instance — connection pooling built in.
    • Java 17 records: Compact representation (Project Valhalla path) — today similar to classes; immutable = safer in concurrent maps.
    • Java 21 virtual threads: Massive I/O concurrency; CPU-bound work still needs platform threads sized to cores.
    • GC evolution: G1 default (9+), ZGC production-ready (15+), generational ZGC (21) — pair with LTS choice.

    Security considerations

    Security implications per LTS:

    • Java 8 EOL risk: No public Oracle updates — vendor JDK (Temurin, Corretto) required; unpatched CVEs on 8 are a compliance finding.
    • Java 11+: TLS 1.3 default improvements; stronger crypto algorithms; removed weak ciphers.
    • Java 17+: Sealed hierarchies prevent unexpected subclasses bypassing security checks in switch handlers.
    • Java 21: Cheap virtual threads — rate-limit at gateway; unbounded task submission is a DoS vector.

    Scalability considerations

    How each LTS changed scalability defaults:

    • Java 8: Thread-per-request with fixed pools — ceiling at hundreds of concurrent I/O waits.
    • Java 11: Async HttpClient — better outbound concurrency without blocking threads (if used async).
    • Java 17: Records reduce allocation churn in hot paths vs mutable POJOs with defensive copies.
    • Java 21: Virtual threads — thread-per-request scales to thousands; K8s HPA on CPU/latency not thread count.

    Production challenges

    Migration blockers teams actually hit:

    • Java 8 → 11: Removed JAXB, Java EE modules — add explicit Maven deps; Nashorn removal breaks scripting.
    • Java 11 → 17: Strong encapsulation of JDK internals — illegal reflective access warnings become errors; Spring Boot 3 jakarta rename.
    • Reflective frameworks: Hibernate, Jackson, Mockito — need versions aligned to target LTS.
    • Library lag: Some vendors lag LTS — verify JDBC driver, gRPC, Netty compatibility before declaring target JDK.
    • Feature vs LTS: Don't run Java 22 in prod for "latest" — use 21 LTS + vendor patches.

    Common mistakes

    • Using parallel streams for I/O — blocks ForkJoinPool; use virtual threads or async HttpClient instead.
    • var for unclear types — var result = service.process() hides return type; use when type is obvious.
    • Mutable records — components are final but a List field can still be mutated; use List.copyOf in constructor.
    • Non-sealed records with switch patterns in Java 21 — missing cases fail at runtime, not compile time.
    • Assuming HttpClient is reactive — send() is blocking; use sendAsync for non-blocking outbound calls.

    Debugging guide

    Version-specific debugging:

    • Verify runtime: java -version and Runtime.version() in app logs at startup.
    • Stream debugging: .peek(x -> log.debug("{}", x)) — remove before prod; use sequential stream when diagnosing parallel bugs.
    • HttpClient: Enable -Djdk.httpclient.HttpClient.log=all for wire-level traces.
    • Record equality surprises: Records equal by component values — ensure BigDecimal scale matches.
    • Virtual thread pinning: JFR event jdk.VirtualThreadPinned on Java 21.
    bash
    # CI matrix — compile and test on target LTS
    java -version
    mvn -Djava.version=21 verify
    # Find Java 8-only APIs
    grep -rn "new Date()" src/ --include="*.java"
    grep -rn "javax\.xml" src/ --include="*.java"

    Best practices

    • Standardize new services on Java 21 LTS; maintain N-1 (17) only during migration windows.
    • Adopt records for DTOs and events; sealed for closed domain hierarchies.
    • Use Streams for collection transforms; imperative loops when stepping debugger or ultra-hot CPU loops.
    • Reuse one HttpClient bean in Spring — inject as singleton.
    • Enable virtual threads for I/O-bound Spring Boot 3.2+ services — measure p99 before and after.
    • Pin vendor JDK (Temurin/Corretto) in Dockerfile — document LTS support end date in ADR.
    • Track CVE feed for your chosen LTS — automate image rebuilds on security patches.

    Anti-patterns

    • Staying on Java 8 for "stability" — highest CVE and hiring friction risk in 2026.
    • parallelStream() everywhere — default pool contention; often slower than sequential.
    • var on every line — readability regression in code review.
    • Mutable record components (exposing modifiable List) — breaks immutability contract.
    • Big-bang 8 → 21 without Spring Boot / jakarta milestone — months of merge hell.

    Staff engineer notes

    • Interviewers test LTS literacy, not trivia — explain *why* Streams beat loops for readability and *when* they don't for performance.
    • Java 11 is the "cleanup" LTS — HttpClient and var are ergonomics; the real breaking change was modular JDK and removed JRE.
    • Java 17 is the "data modeling" LTS — records + sealed are the Java answer to Kotlin data classes and exhaustive when.
    • Java 21 is the "concurrency" LTS — virtual threads obsolete thread-pool tuning for typical Spring MVC services.
    • Migration ADR should list features *adopted* per LTS, not just JDK version bump — features drive value.

    Interview questions

    Interview preparation

    15 questions grouped by difficulty — expand any card for a model answer and follow-up probe.

    Beginner

    5
    1. 1What major features did Java 8 introduce?
      Beginner

      Model answer

      • Lambda expressions and functional interfaces
      • behavior as values. Stream API for declarative collection processing. java.time API replacing Date/Calendar. Optional for explicit null handling. Default and static methods on interfaces
      • enables Stream on Collection without breaking implementations.

      Follow-up probe

      What is a functional interface?

    2. 2What is the Stream API and when would you use it?
      Beginner

      Model answer

      Lazy sequence of elements from a source (collection, array, generator) supporting intermediate ops (filter, map, sorted) and terminal ops (collect, reduce, forEach).

      Use for readable bulk transforms on in-memory data.

      Avoid for simple indexed loops, I/O, or when parallel overhead exceeds benefit.

      Follow-up probe

      Intermediate vs terminal operation?

    3. 3What did Java 11 add for HTTP?
      Beginner

      Model answer

      • java.net.http.HttpClient
      • standard HTTP/1.1 and HTTP/2 client with sync send() and async sendAsync(). Replaces HttpURLConnection and reduces need for Apache HttpClient. Supports WebSocket, configurable timeouts, connection pooling when client reused.

      Follow-up probe

      Is HttpClient blocking?

    4. 4What is var in Java and where can you use it?
      Beginner

      Model answer

      • Local variable type inference
      • compiler infers type from initializer. Legal for local variables and for loops. Illegal for fields, method parameters, return types, or without initializer. Improves readability when type is obvious: var client = HttpClient.newHttpClient(). Added Java 10, common with Java 11 adoption.

      Follow-up probe

      var with null initializer?

    5. 5What is a record in Java 17?
      Beginner

      Model answer

      • Compact immutable data carrier
      • compiler generates constructor, accessors, equals, hashCode, toString. Syntax: record Point(int x, int y). Fields are final. Ideal for DTOs, events, value objects. Can implement interfaces but cannot extend classes.

      Follow-up probe

      Can records have methods?

    Intermediate

    5
    1. 6Explain sealed classes and why use them.
      Intermediate

      Model answer

      • sealed class or interface restricts which classes may extend/implement via permits clause. Compiler knows complete hierarchy
      • enables exhaustive switch in Java 21. Models closed domain: PaymentEvent permits Completed, Failed. Prevents third-party unknown subclasses breaking exhaustive handling.

      Follow-up probe

      sealed vs final?

    2. 7Compare Java 8 lambdas vs Java 21 virtual threads for concurrency.
      Intermediate

      Model answer

      • Lambdas are syntax for anonymous functions
      • used with streams and callbacks, not a concurrency model. Virtual threads are lightweight threads for concurrent task execution
      • especially blocking I/O. Lambdas improve code expression; virtual threads improve scalability of thread-per-request services.

      Follow-up probe

      Lambda vs method reference?

    3. 8Java 17 instanceof pattern matching — how does it work?
      Intermediate

      Model answer

      • if (obj instanceof Payment p)
      • tests type and binds variable p in true branch without cast. Reduces boilerplate and ClassCastException. Precursor to record patterns in switch (Java 21). Works with any type, not only records.

      Follow-up probe

      Negated pattern?

    4. 9What are sequenced collections in Java 21?
      Intermediate

      Model answer

      • Interfaces SequencedCollection, SequencedSet, SequencedMap adding encounter-order API: getFirst(), getLast(), reversed(), addFirst(), addLast(). LinkedHashMap and ArrayList implement them. Expresses that order is part of domain semantics
      • daily settlement batches, audit logs.

      Follow-up probe

      HashMap sequenced?

    5. 10Pattern matching for switch in Java 21 — example?
      Intermediate

      Model answer

      • switch (event) { case Completed(String id, BigDecimal amt) -> settle(id, amt); case Failed(String id, String r) -> alert(id, r); }
      • deconstructs record components in case labels. With sealed permits, compiler checks exhaustiveness. Replaces instanceof chains and visitor boilerplate.

      Follow-up probe

      Guarded patterns?

    Advanced

    5
    1. 11Design a Java version migration plan from 8 to 21.
      Advanced

      Model answer

      • Phase 1: Java 11 on Spring Boot 2.7
      • fix removed APIs (JAXB deps), adopt HttpClient for new code. Phase 2: Spring Boot 3 + Java 17
      • jakarta rename, records for DTOs, sealed events. Phase 3: Java 21
      • virtual threads pilot on I/O service, JFR pinning audit, ZGC flags. CI matrix each phase; vendor JDK pinned; CVE monitoring.

      Follow-up probe

      Biggest 8 to 11 blocker?

    2. 12When would you avoid Streams and use a for-loop?
      Advanced

      Model answer

      Indexed access with break/continue complexity, debugger stepping through business logic, performance-critical tight loops with primitive ops (use for or IntStream carefully), modifying external state with side effects better expressed imperatively, or when team readability standards favor loops for junior maintainers.

      Follow-up probe

      Stream short-circuiting?

    3. 13Records vs Lombok @Value — trade-offs?
      Advanced

      Model answer

      Records: language-native, zero dependency, serialization frameworks support improving, cannot extend classes, compact syntax.

      Lombok: flexible (builders, superbuilders), IDE/plugin dependency, generated bytecode less transparent in stack traces.

      Prefer records for new Java 17+ code; Lombok legacy until migrated.

      Follow-up probe

      Jackson deserialize records?

    4. 14How do virtual threads change Java 11-era async HttpClient strategy?
      Advanced

      Model answer

      • Java 11 async HttpClient avoided blocking platform threads. With Java 21 virtual threads, blocking send() on virtual thread is fine
      • code stays simple, scalability high. Async still wins for composable pipelines and backpressure; virtual threads win for imperative Spring MVC style.

      Follow-up probe

      Combine both?

    5. 15Explain LTS vs feature release strategy for enterprise.
      Advanced

      Model answer

      • Feature releases every 6 months (23, 24, 25...)
      • short support, preview/incubator flags. LTS every 2 years (8,11,17,21,25)
      • vendor backports security fixes ~8+ years. Enterprise pins LTS in BOM and Docker base images; evaluates feature releases in CI only. Reduces ops surprise and CVE patch chaos.

      Follow-up probe

      Oracle vs Temurin?

    Hands-on exercise

    Lab: Java versions feature tour

    • Run the playground — observe Stream output, switch pattern on records, sequenced map lastEntry, virtual thread result.
    • Rewrite the Stream pipeline as an imperative for-loop — compare line count.
    • Add a third sealed record Pending and fix the switch — note compile-time exhaustiveness.
    • Call HttpClient against https://httpbin.org/get (if network allowed) or mock a status string.
    • Bonus: list three APIs removed between Java 8 and 11 that your org might still import.

    JavaJava LTS Versions: 8, 11, 17 & 21

    Starter Templates
    OutputRemote JVM (Piston · Java 15)
    Press Run to execute. Real javac + JVM via remote API. Falls back to simulator on network failure.

    Architecture trade-offs

    • Java 8 streams vs loops: Streams win readability; loops win debugging and primitive hot paths.
    • HttpClient vs RestTemplate/WebClient: HttpClient standard and lightweight; WebClient for Spring reactive stack.
    • Records vs classes: Records for data; classes when inheritance or mutable state required.
    • Java 17 vs 21 LTS: 21 for new work; 17 if Spring Boot 3 on 17 is org policy mid-migration.

    Summary

    Java LTS versions are milestones, not trivia. Java 8 made collection processing declarative; Java 11 standardized HTTP and local inference; Java 17 gave records and sealed types for domain modeling; Java 21 delivers virtual threads and pattern matching for production Spring services. Use this map to plan migrations, answer interview questions by era, and know which feature solves which problem. Next: deep-dive Java 21 LTS enhancements for virtual thread internals and production tuning.

    Key takeaways

    • Java 8: lambdas, streams, java.time — functional style on the JVM.
    • Java 11: HttpClient, var, String helpers — modern JDK without third-party HTTP.
    • Java 17: records, sealed classes — immutable data and closed domain hierarchies.
    • Java 21: virtual threads, sequenced collections, pattern matching — concurrency and exhaustive event handling.
    • Enterprises adopt LTS releases, not every six-month feature drop — plan migrations in phases.
    Ready to mark this lesson complete?Track your journey across the entire course.