Functional interfaces, why lambdas, streams and Optional arrived, filter vs map, intermediate vs terminal operations, loops vs streams, interface changes, String.join, and the headline features of Java 11, 17 and 21.
Published September 25, 2026
"What's new in Java 8?" is asked of nearly every Java candidate. Increasingly, it's followed by "and in 11, 17, 21?". Know which features are final in which LTS release. Mixing up preview and final features is a common, avoidable mistake.
Short answer: An interface with exactly one abstract method (a SAM, "single abstract method"). Its instances can be created with lambdas, method references or constructor references. Examples: Runnable, Comparator<T>, Function<T,R>, Predicate<T>, Supplier<T>, Consumer<T>.
@FunctionalInterface
interface DiscountRule { BigDecimal apply(BigDecimal price); }
DiscountRule tenPercent = p -> p.multiply(new BigDecimal("0.90"));
Function<String, Integer> length = String::length; // method reference
Supplier<List<String>> newList = ArrayList::new; // constructor reference
Key points to cover:
@FunctionalInterface is optional. It makes the compiler reject a second abstract method.default and static methods don't count towards the single method, and nor do methods that redeclare Object methods (such as equals).Learn it in depth → Lambda Expressions
Short answer: Yes, as long as the result still has exactly one abstract method in total. It can extend an interface that has only default or static methods, or one whose single abstract method is the same one it declares. (UnaryOperator<T> extends Function<T,T>, for example.)
interface Named { default String label() { return "rule"; } }
@FunctionalInterface
interface PricingRule extends Named { BigDecimal price(Order o); } // still one abstract method: valid
Key points to cover:
CompletableFuture.andThen, compose, Predicate.and).Common trap: many answer sheets say "no, it can't extend another interface". It can. What it can't end up with is two abstract methods.
Short answer:
java.util.function).Optional.java.time).CompletableFuture, StringJoiner/String.join, collection methods (forEach, removeIf, Map.merge), Nashorn, and Metaspace replacing PermGen.Learn it in depth → Streams API
Optional, lambdas and the Stream API introduced?Short answer:
Optional: to make "this may have no value" explicit in method signatures, reducing accidental NullPointerExceptions.Optional<Customer> customer = repository.findByEmail(email);
String city = customer.map(Customer::address).map(Address::city).orElse("Unknown");
Key points to cover:
Optional as a return type. Don't use it for fields, parameters or collections. Never call get() without checking; use orElse, orElseThrow or map.Learn it in depth → Optional
filter and map in streams?Short answer: filter(predicate) keeps or drops elements, so the stream may get smaller but the element type stays the same. map(function) transforms every element into something else, so the stream keeps the same size but the type may change.
List<String> activeEmails = users.stream()
.filter(User::isActive) // Stream<User>, fewer elements
.map(User::email) // Stream<String>, one email per remaining user
.toList();
Key points to cover:
flatMap maps each element to a stream, and flattens the results. For example, one order becomes many order lines.Short answer:
java.net.http, supporting HTTP/2 and async).var in lambda parameters.java Hello.java).isBlank(), strip(), stripLeading(), stripTrailing(), lines(), repeat().Files.readString/writeString, and Optional.isEmpty().Key points to cover:
Short answer:
instanceof;Common trap: listing "pattern matching for switch" and the "Foreign Function & Memory API" as Java 17 features. In 17 they were a preview and an incubator module respectively. Pattern matching for switch became final in Java 21, and FFM in Java 22.
Learn it in depth → Sealed Classes
Short answer:
getFirst(), getLast(), reversed()).switch.Structured concurrency and scoped values were still previews in 21.
String describe(Shape s) {
return switch (s) { // exhaustive over a sealed hierarchy
case Circle c -> "circle r=" + c.radius();
case Rectangle(var w, var h) -> "rect " + w + "x" + h; // record pattern
};
}
Key points to cover:
main methods, and flexible constructor bodies.Learn it in depth → Virtual Threads
for loop or a stream?Short answer: For simple operations on small or medium collections, a plain loop is usually a little faster. There's no pipeline set-up, no lambda calls, and no boxing. The JIT narrows the gap considerably, though. For most business code, the difference is negligible compared with I/O.
Key points to cover:
Stream<Integer>) are slower than primitive streams (IntStream). Use mapToInt for numeric work.Short answer: Use a stream for declarative data transformations (filter, map, group, aggregate), where readability matters. Use a loop when you need early exit with complex conditions, checked exceptions, index manipulation, updates to several variables, or maximum performance in a hot path.
Short answer: Intermediate operations (filter, map, sorted, distinct, limit) return a new stream and are lazy: nothing runs until a terminal operation is called. Terminal operations (collect, toList, forEach, reduce, count, findFirst) trigger processing, and produce a result or a side effect. After that, the stream is consumed.
Stream<String> s = names.stream().filter(n -> { System.out.println("check " + n); return n.startsWith("A"); });
// nothing printed yet: lazy
s.findFirst(); // now it runs, and stops at the first match (short-circuit)
Key points to cover:
sorted and distinct are stateful intermediate operations. findFirst, anyMatch and limit short-circuit.IllegalStateException.Learn it in depth → Streams API
Short answer: In Java 7, interfaces could have only abstract methods and constants. Java 8 added default methods (inherited, overridable implementations) and static methods. Java 9 added private methods, so default methods can share helper code.
Learn it in depth → Interfaces and Abstract Classes
String.join() used for?Short answer: It concatenates strings with a delimiter, without manual loops or trailing separators.
String.join(", ", List.of("Java", "Spring", "SQL")); // "Java, Spring, SQL"
String.join("/", "api", "v1", "orders"); // "api/v1/orders"
names.stream().collect(Collectors.joining(", ", "[", "]")); // with a prefix and suffix
Q: What are the four core functional interfaces?
A: Supplier<T> (no input → T), Consumer<T> (T → nothing), Function<T,R> (T → R) and Predicate<T> (T → boolean), plus their Bi… and primitive variants (IntPredicate, ToLongFunction, …).
Q: What does "effectively final" mean for lambdas? A: A lambda can use local variables from its enclosing scope only if they are never reassigned. The lambda captures a copy of the value, so allowing mutation would be confusing and unsafe across threads.
Q: map vs flatMap on Optional?
A: map wraps the function's result in an Optional. flatMap expects the function to return an Optional already, which avoids Optional<Optional<T>>.
Q: What replaced Date and Calendar?
A: The immutable, thread-safe java.time API: LocalDate, LocalDateTime, ZonedDateTime, Instant, Duration, Period and DateTimeFormatter.