Java 8 Features: The Interview Overview
Every Java 8 feature an interviewer asks about, why each was added, and the one-sentence answer for each — lambdas, streams, Optional, default methods, java.time, CompletableFuture and Metaspace.
On this page
Java 8 shipped in 2014 and is still the version most interview question banks are built around. This page is the map: what each feature is, the problem it solved, and the compact answer to give. The topics that follow in this phase go deep on each one.
Key Takeaways
- Lambdas are the foundation — streams,
CompletableFutureand the functional interfaces all rest on them. - Default methods exist so
Collectioncould gainstream()without breaking every existing implementation. Optionalis a return type, not a general-purpose null replacement.java.timereplacedDateandCalendar, which were mutable and not thread-safe.- Metaspace replaced PermGen: class metadata moved to native memory and grows on demand.
The feature map
Lambdas and method references
A lambda is an anonymous implementation of a functional interface — an interface with exactly one abstract method.
// Java 7
Collections.sort(names, new Comparator<String>() {
@Override public int compare(String a, String b) {
return a.length() - b.length();
}
});
// Java 8
names.sort(Comparator.comparingInt(String::length));The line to have ready: "a lambda is not syntactic sugar for an anonymous class — the compiler emits
an invokedynamic instruction and the JVM builds the implementation at first call, so a
non-capturing lambda allocates nothing and generates no extra class file." That distinction is
covered in full in Lambdas and invokedynamic.
Method references come in four shapes, and being able to name them is a common quick check:
| Form | Example |
|---|---|
| Static method | Integer::parseInt |
| Instance method of a particular object | System.out::println |
| Instance method of an arbitrary object of a type | String::length |
| Constructor | ArrayList::new |
Streams
A stream is a pipeline over a source, not a data structure. It holds no elements, does not modify the source, and does nothing at all until a terminal operation runs.
Map<String, Long> ordersPerRegion = orders.stream()
.filter(o -> o.total().compareTo(THRESHOLD) > 0) // intermediate, lazy
.collect(Collectors.groupingBy(Order::region, Collectors.counting())); // terminalThe three properties interviewers check: streams are lazy (intermediate operations are recorded,
not executed), single-use (consuming one twice throws IllegalStateException), and they
do not mutate the source. Detail lives in
Stream API fundamentals.
Optional
Optional is a container that may hold a value, designed to make "no result" explicit in a method
signature.
public Optional<Customer> findByEmail(String email) { ... }
String name = repository.findByEmail(email)
.map(Customer::name)
.orElse("unknown");The point interviewers look for is knowing what it is not for: not a field type, not a parameter
type, and not a wrapper you .get() immediately — that is a NullPointerException with more
ceremony. See Optional best practices.
Default and static methods on interfaces
Adding stream() to Collection in Java 8 would have broken every existing implementation in the
world. Default methods made it possible to add behaviour to a published interface without breaking
implementers:
public interface Collection<E> extends Iterable<E> {
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
// removeIf, forEach and spliterator were all added the same way
}The follow-up is always the diamond problem: if two interfaces provide the same default method, the
implementing class must override it and can delegate with Interface.super.method(). And a class
method always beats an interface default. Covered in
Default and static methods.
java.time
java.util.Date was mutable, had months numbered from zero, mixed date and time in one type, and its
formatter (SimpleDateFormat) was not thread-safe — a genuine source of production corruption when
shared as a static field.
LocalDate date = LocalDate.of(2026, 8, 8); // no time, no zone
LocalTime time = LocalTime.of(14, 30); // no date
LocalDateTime local = LocalDateTime.of(date, time); // no zone — ambiguous instant
ZonedDateTime zoned = local.atZone(ZoneId.of("Europe/London"));
Instant timestamp = zoned.toInstant(); // a point on the UTC timeline
Duration elapsed = Duration.ofMinutes(90); // machine time
Period age = Period.between(date, LocalDate.now()); // human timeEvery type is immutable and thread-safe, and DateTimeFormatter is safe to share. Full treatment in
The java.time API.
CompletableFuture
Future from Java 5 could only be polled or blocked on. CompletableFuture can be composed:
CompletableFuture<Quote> quote = CompletableFuture
.supplyAsync(() -> pricingClient.fetch(sku), ioPool)
.thenCombine(CompletableFuture.supplyAsync(() -> stockClient.fetch(sku), ioPool),
Quote::of)
.orTimeout(2, TimeUnit.SECONDS)
.exceptionally(ex -> Quote.unavailable(sku));The interview angle is usually thenApply versus thenCompose (map versus flatMap), and which
thread the callbacks run on. See
CompletableFuture and async composition.
The smaller ones that still come up
Map default methods. getOrDefault, putIfAbsent, computeIfAbsent, merge. The last two
replace the read-check-write pattern and are atomic on ConcurrentHashMap:
counts.merge(key, 1L, Long::sum); // increment or insert
index.computeIfAbsent(key, k -> new ArrayList<>()).add(value); // multimap in one line
config.getOrDefault("timeout", "30s");
cache.putIfAbsent(key, expensive()); // note: expensive() runs regardlessStringJoiner and String.join. Delimiters without the trailing-separator bug.
Repeating annotations and type annotations, which is what enabled tools like Checker
Framework and the @NonNull conventions many teams use.
Nashorn, the JavaScript engine — worth knowing only because it was removed again in Java 15.
Metaspace. Class metadata moved out of the heap into native memory. OutOfMemoryError: PermGen space is gone; OutOfMemoryError: Metaspace took its place, and because Metaspace grows on demand a
classloader leak will now consume machine memory until the container is killed. Setting
-XX:MaxMetaspaceSize is still the right thing to do.
How to use this page
Treat it as the index for the rest of the phase. In an interview, the shape that works is: name the
feature, say what problem it solved, then give one concrete example. "Default methods let the JDK add
stream() to Collection without breaking every implementation" beats "default methods let
interfaces have bodies" every time, because it demonstrates you know why the feature exists.
Frequently Asked Questions
Why is Java 8 still the version interviews focus on?
Did Java 8 remove PermGen?
What is the single most important Java 8 feature?
Related tutorials
- Lambdas and invokedynamic: How They Really WorkWhy a lambda is not an anonymous class, what invokedynamic and LambdaMetafactory do at first call, the allocation difference between capturing and non-capturing lambdas, and what this means.
- Functional Interfaces: Function, Predicate, Supplier, ConsumerThe four core shapes and how to derive the other thirty-nine, why primitive specialisations exist, compose versus andThen, and the checked-exception problem with a clean workaround.
- Stream API Fundamentals: Lazy PipelinesHow a stream pipeline actually executes: why nothing runs until the terminal operation, what short-circuiting really means, stateful versus stateless operations, and why a stream is single-use.
- Collectors, groupingBy and Downstream CollectorsThe collector API in depth: multi-level groupingBy, downstream collectors, the toMap duplicate-key exception, the null-value trap, teeing and flatMapping, and writing a Collector by hand.