Skip to content
JavaAgentic

Type at least two characters. Try “RAG”, “pgvector” or “tool calling”.

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.

Beginner5 min readUpdated
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, CompletableFuture and the functional interfaces all rest on them.
  • Default methods exist so Collection could gain stream() without breaking every existing implementation.
  • Optional is a return type, not a general-purpose null replacement.
  • java.time replaced Date and Calendar, 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 default methods are the enablers; streams, collectors and CompletableFuture are what they made possible.

Lambdas and method references

A lambda is an anonymous implementation of a functional interface — an interface with exactly one abstract method.

before and after
// 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:

FormExample
Static methodInteger::parseInt
Instance method of a particular objectSystem.out::println
Instance method of an arbitrary object of a typeString::length
ConstructorArrayList::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.

a pipeline
Map<String, Long> ordersPerRegion = orders.stream()
        .filter(o -> o.total().compareTo(THRESHOLD) > 0)   // intermediate, lazy
        .collect(Collectors.groupingBy(Order::region, Collectors.counting()));  // terminal

The 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.

the intended use
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:

Collection.java, roughly
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.

the replacement types
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 time

Every 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:

composition instead of blocking
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:

the four that matter
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 regardless

StringJoiner 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?
Because it was the largest change to how Java code is written since generics, and because an enormous amount of production code is still on it or was written in its idiom. Java 11, 17 and 21 added important runtime features, but they did not change day-to-day coding style the way lambdas and streams did. Interviewers use Java 8 as a proxy for whether you write modern Java at all.
Did Java 8 remove PermGen?
Yes. Class metadata moved from a fixed-size PermGen region inside the heap to Metaspace, which lives in native memory and grows on demand. The practical effect is that java.lang.OutOfMemoryError PermGen space no longer exists, but an unbounded classloader leak now consumes native memory instead — so you should still set MaxMetaspaceSize.
What is the single most important Java 8 feature?
Lambdas, because everything else depends on them. Streams need them, CompletableFuture needs them, and default methods exist mainly so the Collection interfaces could gain stream() and forEach() without breaking every implementation in existence. If you understand why lambdas required an invokedynamic-based implementation, the rest of the release follows.

Related tutorials