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.
On this page
- Picture it first: four LTS releases, ten years
- Lambdas and functional interfaces
- Method references: the four kinds
- Streams: a lazy pipeline, one element at a time
- 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
🚀 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)
- Functional programming ideas in depth: Functional programming in Java and Module: Functional Java
- Virtual threads, structured concurrency and the JMM: Multithreading and the JMM: the must-knows
- Collections internals (HashMap, ConcurrentHashMap): Collections and HashMap: the must-knows
- Preview features beyond Java 21 and the newer releases in full: Module: Modern Java
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
orElseevaluates 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.
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
mainmethods (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,IntStreamand friends work onintdirectly.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
finalor 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 capturesthis, not a copy of the field. thisinside 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::compareTofits aComparator<String>(two arguments) because the first argument becomesthis. That's whyBiFunction<String, String, Boolean> f = String::equals;compiles. - A bound reference evaluates its receiver once, when the reference is created.
foo()::barcallsfoo()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)
- java.util.stream package summary (Java SE 21 API)
- Collectors (Java SE 21 API)
- Optional (Java SE 21 API)
- JEP 441: Pattern Matching for switch
- JEP 440: Record Patterns
- JEP 444: Virtual Threads
- JEP 431: Sequenced Collections
- The Java Language Specification, Java SE 21 (lambdas 15.27, records 8.10, sealed classes 8.1.1.2)