When an interviewer asks what is new in Java, listing every release note is not the goal. They want evidence that you can read and write Java used in modern codebases. Six feature groups carry most of that conversation: lambdas, streams, Optional, records, sealed types with pattern matching, and virtual threads.
The answer that lands names what a feature replaced, shows one short example, and admits where it stops helping. A version number recited on its own proves nothing about what you can build.
Lambdas and functional interfaces in Java 8
A functional interface has one abstract method. Java 8 lambdas let you supply that behaviour without an anonymous-class wrapper.
Before Java 8, sorting names by length could look like this:
names.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
});With a lambda:
names.sort((a, b) -> Integer.compare(a.length(), b.length()));The target type is Comparator<String>, so the compiler knows the parameter types and the method contract. Standard functional interfaces include Predicate<T>, Function<T,R>, Consumer<T> and Supplier<T>.
The interview trap is to say that a lambda is simply a shorter anonymous class. Both can provide behaviour, but their scoping and identity semantics are not identical. In a lambda, this refers to the enclosing instance. Lambdas also work naturally as inputs to stream operations.
Streams: a pipeline, not a collection
Streams arrived in Java 8. A stream processes elements through a pipeline and does not store the data itself. Consider a list of scores:
List<Integer> scores = List.of(42, 71, 58, 85, 39);
List<Integer> adjusted = scores.stream()
.filter(score -> score >= 50)
.map(score -> score + 5)
.sorted()
.toList();The exact result is [63, 76, 90]. Filtering keeps 71, 58 and 85. Mapping adds 5 to produce 76, 63 and 90. Sorting produces 63, 76 and 90. The source list still holds all five values, because a stream reads its source rather than writing back to it.
Intermediate operations such as filter(), map() and sorted() are lazy. They build a pipeline. A terminal operation such as toList(), collect(), count() or reduce() triggers traversal.
Two follow-ups appear repeatedly:
Can a stream be reused? No. After a terminal operation, the stream is consumed. Create a new stream from the source.
Are parallel streams always faster? No. Splitting, coordination and ordering have costs. The data size, operation cost and shared state determine whether parallel execution helps.
Streams are excellent for transformations with clear, side-effect-free steps. A simple loop is often clearer when control flow, checked exceptions or mutation dominates the work.
Optional and the null-handling contract
Optional<T> also arrived in Java 8. It represents a result that may be present or absent, making that possibility visible in a return type.
Optional<User> user = repository.findByEmail(email);
String displayName = user
.map(User::displayName)
.orElse("Guest");Prefer orElseGet() when producing the fallback is expensive, because its supplier runs only when the optional is empty. Avoid calling get() without a presence check, because that recreates the unsafe assumption the type was meant to remove.
Do not turn every field, parameter and local variable into an Optional. Its clearest mainstream use is as a return type for “a value may be absent”. It is not a universal replacement for sensible validation or empty collections.
Records and compact data carriers
Records became a permanent language feature in Java 16. They replace much of the boilerplate in classes whose main job is to carry data:
record Candidate(String name, int score) {}That declaration provides component accessors, a canonical constructor, and value-based equals(), hashCode() and toString() behaviour. You can add validation in a compact constructor:
record Candidate(String name, int score) {
Candidate {
if (score < 0) throw new IllegalArgumentException("negative score");
}
}A record is shallowly immutable. Its component fields cannot be reassigned, but a component can still refer to a mutable list. A strong interview answer mentions that distinction instead of promising deep immutability. The same care applies to String handling in Java, where the object itself never changes even though the variable holding it can be pointed somewhere else.
Sealed classes and pattern matching
Sealed classes became permanent in Java 17. They restrict which types may extend or implement a type, making a closed domain model explicit.
sealed interface Payment permits Card, Upi {}
record Card(String lastFour) implements Payment {}
record Upi(String handle) implements Payment {}Java 21 made pattern matching for switch permanent. The compiler can use the closed hierarchy to check whether the cases cover every permitted subtype:
String label = switch (payment) {
case Card card -> "Card ending " + card.lastFour();
case Upi upi -> "UPI " + upi.handle();
};The modelling pitch is more important than the syntax. Sealing says, “these are the allowed variants”, records express compact values, and pattern matching consumes the variants without manual casts. Pattern matching for instanceof, permanent since Java 16, removes the older test-then-cast repetition in the same spirit.
Virtual threads in Java 21
Virtual threads became permanent in Java 21. They are lightweight threads managed by the Java runtime and are useful when an application needs very many concurrent tasks that spend substantial time waiting for I/O.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> result = executor.submit(() -> fetchProfile(userId));
System.out.println(result.get());
}The code keeps the familiar thread-per-task style without requiring one operating-system thread per task. That can improve scalability for blocking network or database work. How the operating system schedules the carrier threads underneath is the layer covered across CS Fundamentals.
Virtual threads do not make CPU-bound code compute faster. They also do not remove the need to control downstream pressure on a database or API. Concurrency may become cheaper, but scarce external resources remain scarce.
Which Java version should you claim?
Claim the version you have actually used, then describe the features you can explain.
Java 8: lambdas, streams and
Optional.Java 10: local variable type inference with
var.Java 16: records, and pattern matching for
instanceof.Java 17: sealed classes.
Java 21: pattern matching for
switch, and virtual threads.
var does not make Java dynamically typed. The compiler still infers one static local-variable type, and it is best used when the right side makes that type obvious.
Read the job description for the employer's baseline. Many teams standardise on an LTS release, but “LTS” alone does not tell you which one that codebase runs. It is honest to say, “I worked on Java 17 and have built small examples with Java 21 virtual threads,” if that is true. The interview layers that sit around the language, from aptitude practice to mock interviews, are what the Placement Preparation courses cover.
The short version and next step
Start with Java 8's functional style, then add modern data modelling and concurrency. Explain a lambda's target interface, a stream's lazy pipeline, Optional as an absence contract, record immutability, sealed exhaustiveness and the I/O use case for virtual threads.
Use the Complete Java Course if you want that path already built, from Java 8's functional style through the modern data and concurrency features. In an interview, one correct example plus one honest limitation is the modern-Java answer that sounds like experience rather than memorisation.




