Course contents

Java 8 to 21: the must-knows

A dense revision sheet on modern Java for interviews: lambdas, functional interfaces and method references, lazy stream pipelines and collectors, Optional done right, default methods, var, switch expressions, text blocks, records, sealed types, pattern matching, virtual threads, sequenced collections, and what each LTS from 8 to 21 changed.

PremiumMust know for interviewsLesson24 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: four LTS releases, ten years
  2. Lambdas and functional interfaces
  3. Method references: the four kinds
  4. Streams: a lazy pipeline, one element at a time
  5. Collectors you must write from memory
  6. Optional done right
  7. Interfaces: default and static methods
  8. Modern syntax: var, switch expressions, text blocks
  9. Records, sealed types and pattern matching
  10. Virtual threads and sequenced collections (Java 21)
  11. Senior probes: where candidates lose points
  12. How to say it in the interview

🚀 What is this sheet?

It's every language and library change from Java 8 to Java 21 that an interviewer expects you to use fluently: lambdas and streams first, because they're in every coding round, then the modern features (records, sealed types, pattern matching, virtual threads) that senior rounds use to check whether your Java stopped in 2014. Each item has the must-say fact, a small example, and a link to the full lesson. If you can write a groupingBy from memory and explain why a switch over a sealed interface needs no default, you're in good shape.

For this sheet, we'll start with the Java 8 core (functional interfaces, method references, streams, collectors, Optional) because that's where most marks are won or lost. Then we'll move through the features in release order and finish with the LTS summary, the traps, and the talk track. All code compiles and runs on Java 21, and I mark the version each feature arrived in, because "which version added records?" is a real question.

Below the line (out of scope)

The 30-second answer

Java 8 made Java functional: lambdas implement single-method functional interfaces like Function, Predicate, Supplier and Consumer, and streams run lazy pipelines where nothing executes until a terminal operation pulls elements through one at a time. Optional is for return types that might be empty, and default methods let interfaces evolve. Java 10 to 17 cleaned up the syntax: var, switch expressions, text blocks, records for immutable data and sealed classes for closed hierarchies. Java 21 joins them with pattern matching for switch and record patterns, so the compiler can prove a switch is exhaustive, and it adds virtual threads, which make thread-per-request cheap for I/O-bound code, plus sequenced collections with getFirst, getLast and reversed.

What you’ll learn

  • Name the core functional interfaces, the four kinds of method reference, and why lambdas capture only effectively final locals
  • Explain a lazy stream pipeline: intermediate vs terminal, stateless vs stateful, short-circuiting, and why a stream can't be reused
  • Use groupingBy, partitioningBy, toMap and joining correctly, including the duplicate-key trap and the parallel-stream common pool trap
  • Apply Optional as a return type only, and know when orElse evaluates its argument
  • Write records, sealed hierarchies and pattern-matching switches that the compiler checks for exhaustiveness
  • Summarise what Java 8, 11, 17 and 21 each brought, including virtual threads and sequenced collections

Interview radar

Asked very often: Write a groupingBy. Intermediate vs terminal operations. map vs flatMap. What's a functional interface? orElse vs orElseGet. Records vs Lombok or a regular class. Senior rounds probe: Why lambdas need effectively final variables, the toMap duplicate-key exception, parallel streams and the common pool, the default-method diamond rule, sealed plus switch exhaustiveness, and when virtual threads don't help. Production angle: A parallel stream doing HTTP calls that stalls every other parallel task, an Optional.get() that throws in production, and records holding mutable lists.

01Picture it first: four LTS releases, ten years

Think of modern Java as four big stops on a train line. Most companies jump from one long-term support (LTS) release to the next and skip the six-monthly releases in between, so interviewers frame questions as "8 vs 11 vs 17 vs 21". The timeline below shows each LTS with its headline features, and the in-between releases where most of the syntax actually landed.

Fig. 1

A timeline from Java 8 in 2014 to Java 25 in 2025 marking the LTS releases 8, 11, 17, 21 and 25 with their headline features, and the non-LTS releases where var, switch expressions, text blocks, records and unnamed variables arrived

A timeline from Java 8 in 2014 to Java 25 in 2025 marking the LTS releases 8, 11, 17, 21 and 25 with their headline features, and the non-LTS releases where var, switch expressions, text blocks, records and unnamed variables arrived
LTS Released What you must associate with it
Java 8 Mar 2014 Lambdas, method references, streams, Optional, default and static interface methods, java.time, CompletableFuture, Metaspace replaces PermGen
Java 11 Sep 2018 Brings 9 and 10 along: modules, List.of, JShell, G1 default, var. Adds var in lambda parameters, standard HttpClient, String.isBlank/strip/lines/repeat, Files.readString, running java Hello.java directly, JFR in OpenJDK
Java 17 Sep 2021 Brings 12 to 16: switch expressions (14), helpful NPEs (14), text blocks (15), records and instanceof patterns (16), Stream.toList() (16). Adds sealed classes and strong encapsulation of JDK internals
Java 21 Sep 2023 Virtual threads, pattern matching for switch, record patterns, sequenced collections, generational ZGC; previews of structured concurrency, scoped values and unnamed patterns
  • Version note: Java 25 (Sep 2025) is the newest LTS. It finalised scoped values, compact source files and instance main methods (Compact source files), and flexible constructor bodies. Interviews still center on 17 and 21, so this sheet stays on 21.

02Lambdas and functional interfaces

A functional interface is an interface with exactly one abstract method; a lambda is a compact implementation of that one method. @FunctionalInterface is optional, but it makes the compiler reject a second abstract method. Methods inherited from Object (like equals on Comparator) and default methods don't count. Under the hood, javac emits an invokedynamic call and the JVM builds the implementation at runtime through LambdaMetafactory, so unlike anonymous classes there's no Outer$1.class file per lambda.

Interface Method Shape Typical use
Function<T, R> R apply(T t) T to R map(User::email)
BiFunction<T, U, R> R apply(T t, U u) (T, U) to R map.merge(k, v, Integer::sum)
UnaryOperator<T> T apply(T t) T to T list.replaceAll(String::trim)
BinaryOperator<T> T apply(T a, T b) (T, T) to T reduce(0, Integer::sum)
Predicate<T> boolean test(T t) T to boolean filter(o -> o.isPaid())
Supplier<T> T get() () to T orElseGet(this::loadDefault)
Consumer<T> void accept(T t) T to nothing forEach(System.out::println)
  • Primitive specializations avoid boxing: IntPredicate, ToIntFunction, IntStream and friends work on int directly. Stream<Integer>.map(...) boxes every element; mapToInt(...) doesn't.
  • Composition: Predicate.and/or/negate, Function.andThen/compose, Comparator.comparing(...).thenComparing(...).reversed(). Functional interfaces in Java
  • Effectively final capture: a lambda can read a local variable only if it's final or never reassigned. The lambda receives a copy of the value, and it may run later or on another thread, so allowing reassignment would make that copy silently stale and racy. Fields aren't restricted, because the lambda captures this, not a copy of the field.
  • this inside a lambda is the enclosing instance; inside an anonymous class it's the anonymous object. A lambda also can't redeclare a local variable name from the enclosing scope. Inner, static nested and anonymous classes
int count = 0;
List.of("a", "b").forEach(s -> count++);          // compile error: count is not effectively final

int[] box = {0};
List.of("a", "b").forEach(s -> box[0]++);         // compiles, but it's a smell (and racy in parallel)

long count2 = Stream.of("a", "b").count();        // the right answer: let the stream count

03Method references: the four kinds

A method reference is a lambda whose body only calls one existing method. Interviewers ask for the four kinds because the third one confuses people.

Kind Syntax Equivalent lambda
Static method Integer::parseInt s -> Integer.parseInt(s)
Instance method of a particular object (bound) System.out::println, prefix::concat x -> System.out.println(x)
Instance method of an arbitrary object of a type (unbound) String::toUpperCase s -> s.toUpperCase() (the first argument becomes the receiver)
Constructor ArrayList::new, int[]::new () -> new ArrayList<>(), n -> new int[n]
  • Unbound references shift the receiver: String::compareTo fits a Comparator<String> (two arguments) because the first argument becomes this. That's why BiFunction<String, String, Boolean> f = String::equals; compiles.
  • A bound reference evaluates its receiver once, when the reference is created. foo()::bar calls foo() immediately; x -> foo().bar(x) calls it every time.

04Streams: a lazy pipeline, one element at a time

Premium lesson

Keep reading with the Java Pass

The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.

  • Collectors you must write from memory
  • Optional done right
  • Interfaces: default and static methods
  • Modern syntax: var, switch expressions, text blocks
  • Records, sealed types and pattern matching
  • Virtual threads and sequenced collections (Java 21)
  • Senior probes: where candidates lose points
  • How to say it in the interview

From ₹199 · one-time payment · No auto-renewal

Interview questions for this topicComing soon

Further reading (8)