Java 8+

Practice commonly asked Java 8+ interview questions with clear answers and explanations.

30 Interview Questions

Java 8+ Interview Questions

30 Questions
Q 1

How do you handle Checked Exceptions inside Java Streams and Lambda expressions?

Hard
Answer
Standard functional interfaces (Function, Consumer, Predicate) do not declare checked exceptions in their signatures. Checked exceptions must be caught and re-thrown as unchecked RuntimeExceptions within the lambda body, or wrapped inside custom throwing functional interface adapter utilities.
Explanation
Writing a generic wrapper function like rethrowFunction(ThrowingFunction) avoids repetitive try-catch blocks inside stream pipelines.
Code Example Java
List<String> filePaths = List.of("a.txt", "b.txt");
// Catching and re-throwing as unchecked exception inside lambda:
List<String> contents = filePaths.stream()
    .map(path -> {
        try { return Files.readString(Path.of(path)); }
        catch (IOException e) { throw new UncheckedIOException(e); }
    })
    .toList();
Reference: Java Functional Programming & Streams API
Q 2

What is the var keyword in Lambda parameters (Java 11+) and why is it useful?

Medium
Answer
Java 11 allowed using 'var' for lambda parameters ((var x, var y) -> ...). Its primary benefit is allowing annotations (such as @Nonnull, @Nullable) to be attached to lambda parameters without requiring explicit type names.
Explanation
If var is used for one parameter in a lambda, it must be used for ALL parameters in that lambda.
Code Example Java
// Attaching annotations to inferred lambda parameters:
BiFunction<String, String, String> concat = (@Nonnull var a, @Nonnull var b) -> a + b;
Reference: Java Functional Programming & Streams API
Q 3

What is the difference between findFirst() on an unordered stream vs an ordered stream?

Medium
Answer
On an ordered stream (e.g. List source), findFirst() strictly returns the first element in encounter order. On an unordered stream (e.g. HashSet source or after calling stream.unordered()), findFirst() may return any arbitrary element since encounter order is undefined.
Explanation
If encounter order is not needed in parallel streams, call unordered() to allow findFirst() to return faster without thread coordination.
Code Example Java
Set<String> set = Set.of("X", "Y", "Z"); // Unordered
Optional<String> first = set.stream().findFirst(); // Any element (no encounter order)
Reference: Java Functional Programming & Streams API
Q 4

How does Method Reference ClassName::new work with Generic Factories?

Medium
Answer
Constructor references (ClassName::new) bind to matching Functional Interface constructors (e.g. Supplier<T>, Function<T, R>, BiFunction<T, U, R>), allowing generic factory implementations without reflection.
Explanation
Constructor references avoid boilerplate factory classes and integrate smoothly with collections suppliers.
Code Example Java
interface UserFactory {
    User create(String name, int age);
}
class User {
    public User(String name, int age) {}
}
// Method reference implementation of factory:
UserFactory factory = User::new;
User u = factory.create("Alice", 25);
Reference: Java Functional Programming & Streams API
Q 5

What is Collectors.flatMapping() and how does it solve nested collection grouping?

Hard
Answer
Collectors.flatMapping() applies a 1-to-many flatMap function to each input element before passing flattened elements to the downstream collector, enabling grouping of nested lists directly into flat Sets/Lists.
Explanation
Introduced in Java 9 to simplify multi-level stream aggregations with collections of collections.
Code Example Java
record Author(String genre, List<String> books) {}
List<Author> authors = getAuthors();
// Groups all unique book titles by genre:
Map<String, Set<String>> booksByGenre = authors.stream()
    .collect(Collectors.groupingBy(
        Author::genre,
        Collectors.flatMapping(a -> a.books().stream(), Collectors.toSet())
    ));
Reference: Java Functional Programming & Streams API
Q 6

What is the difference between sequential stream and parallel stream overhead for small collections?

Medium
Answer
For small collections ($N < 10,000$), parallel streams are often slower than sequential streams due to thread synchronization, task splitting/forking overhead, and cache-line invalidation across CPU cores.
Explanation
The NQ model ($N \times Q$) determines parallel efficiency: $N$ is element count, $Q$ is CPU computation cost per element. Parallel streams win when $N \times Q > 10,000$.
Code Example Java
List<Integer> smallList = List.of(1, 2, 3, 4, 5);
// Sequential is faster for small lists:
int sum = smallList.stream().mapToInt(i -> i * 2).sum();
Reference: Java Functional Programming & Streams API
Q 7

What is Collectors.mapping() and where is it used as a downstream collector?

Medium
Answer
Collectors.mapping(mapper, downstreamCollector) applies a transformation mapping function to each stream element before passing it to the downstream collector, commonly used inside groupingBy.
Explanation
It allows grouping by one property while collecting a different extracted property into lists or sets.
Code Example Java
record Student(String grade, String name) {}
List<Student> students = getStudents();
// Groups by grade and extracts only names into a List<String>:
Map<String, List<String>> namesByGrade = students.stream()
    .collect(Collectors.groupingBy(Student::grade, Collectors.mapping(Student::name, Collectors.toList())));
Reference: Java Functional Programming & Streams API
Q 8

What is Optional.or() introduced in Java 9?

Medium
Answer
Optional.or(Supplier<? extends Optional<? extends T>>) returns the current Optional if a value is present, or returns an alternative Optional produced by the supplier if empty, enabling lazy chaining of Optional fallbacks.
Explanation
Unlike orElseGet() which returns the raw value unwrapped, or() returns another Optional<T>, allowing continued optional chaining.
Code Example Java
Optional<User> user = findInCache(id)
    .or(() -> findInDatabase(id))
    .or(() -> findInRemoteService(id));
Reference: Java Functional Programming & Streams API
Q 9

What is the difference between IntBinaryOperator and BinaryOperator<Integer>?

Easy
Answer
IntBinaryOperator works on primitive int types directly (int applyAsInt(int left, int right)), avoiding autoboxing and heap memory allocations. BinaryOperator<Integer> operates on boxed Integer wrapper objects.
Explanation
Primitive functional interfaces (IntConsumer, LongFunction, DoublePredicate) should always be used in performance-critical numeric loops.
Code Example Java
IntBinaryOperator primitiveAdd = (a, b) -> a + b; // Zero heap allocation
BinaryOperator<Integer> boxedAdd = (a, b) -> a + b; // Autoboxing overhead
Reference: Java Functional Programming & Streams API
Q 10

What is Stream.iterate(T seed, Predicate hasNext, UnaryOperator next) introduced in Java 9?

Medium
Answer
Java 9 added a 3-argument Stream.iterate() that includes a termination predicate (Predicate hasNext), functioning identically to a standard procedural for-loop (for(seed; hasNext; next)).
Explanation
Unlike Java 8 iterate() which was infinite without limit(), Java 9 iterate() terminates naturally when the predicate evaluates to false.
Code Example Java
// Equivalent to for (int i = 0; i < 10; i += 2)
Stream.iterate(0, i -> i < 10, i -> i + 2)
    .forEach(System.out::println);
Reference: Java Functional Programming & Streams API
Q 11

What is the difference between Stream.reduce() and Stream.collect()?

Hard
Answer
reduce() is an immutable reduction: it combines elements by repeatedly creating new result values. collect() is a mutable reduction: it mutates an existing container (e.g. adding items to an ArrayList or StringBuilder) without allocating new accumulator objects at every step.
Explanation
Using reduce() to concatenate strings or build lists creates huge object churn ($O(N^2)$ memory copying). Use collect() for mutable container accumulations.
Code Example Java
// Inefficient reduce (creates new string every step):
// stream.reduce("", (s1, s2) -> s1 + s2);

// Efficient mutable collect (single container):
String res = Stream.of("a", "b", "c").collect(Collectors.joining());
Reference: Java Functional Programming & Streams API
Q 12

What is the Spliterator.characteristics() bitmask and how does it optimize stream pipelines?

Hard
Answer
Spliterator characteristics return a bitmask of properties (ORDERED, DISTINCT, SORTED, SIZED, NONNULL, IMMUTABLE, CONCURRENT, SUBSIZED) that inform the stream execution engine to skip redundant operations (e.g. skipping sorting if SORTED, skipping element traversal for count() if SIZED).
Explanation
Custom spliterators should report characteristics accurately to maximize execution performance in both sequential and parallel streams.
Code Example Java
Set<String> set = Set.of("A", "B");
// Spliterator reports DISTINCT | SIZED
Spliterator<String> sp = set.spliterator();
boolean isDistinct = sp.hasCharacteristics(Spliterator.DISTINCT); // true
Reference: Java Functional Programming & Streams API
Q 13

What is the difference between Function.identity() and Comparator.naturalOrder()?

Easy
Answer
Function.identity() is a function returning its input argument unchanged (t -> t). Comparator.naturalOrder() is a comparator that compares Comparable objects according to their natural compareTo() implementation.
Explanation
Comparator.reverseOrder() provides the inverse of naturalOrder().
Code Example Java
List<Integer> list = List.of(3, 1, 4, 1, 5);
List<Integer> sorted = list.stream().sorted(Comparator.naturalOrder()).toList(); // [1, 1, 3, 4, 5]
Reference: Java Functional Programming & Streams API
Q 14

What is Stream.mapMultiToInt() and mapMultiToDouble() in Java 16?

Hard
Answer
They are primitive specializations of mapMulti() that push primitive int or double elements directly into primitive downstream sinks (IntConsumer/DoubleConsumer), eliminating both stream allocations and primitive boxing.
Explanation
Ideal for unpacking arrays of primitive values inside objects directly into numeric streams.
Code Example Java
record Transaction(int[] amounts) {}
List<Transaction> txs = getTransactions();
int sum = txs.stream()
    .mapMultiToInt((tx, sink) -> { for (int a : tx.amounts()) sink.accept(a); })
    .sum();
Reference: Java Functional Programming & Streams API
Q 15

Why should streams not be reused once a terminal operation has been executed?

Easy
Answer
Streams represent single-use pipelines. Once a terminal operation is executed, the stream is considered consumed and closed; attempting to perform another operation on the same stream instance throws an IllegalStateException.
Explanation
To process the same data multiple times, obtain a new stream instance from the source collection or use a Supplier<Stream<T>>.
Code Example Java
Stream<String> stream = List.of("A", "B").stream();
stream.forEach(System.out::println); // Consumes stream
// stream.count(); // Throws IllegalStateException: stream has already been operated upon or closed
Reference: Java Functional Programming & Streams API
Q 16

What is Comparator.nullsFirst() and Comparator.nullsLast()?

Medium
Answer
They are null-safe comparator wrappers that place null elements at the beginning (nullsFirst) or at the end (nullsLast) of a sorted sequence without throwing NullPointerException.
Explanation
They accept a downstream comparator to sort the non-null elements among themselves.
Code Example Java
List<String> list = Arrays.asList("Banana", null, "Apple", null);
list.sort(Comparator.nullsLast(String::compareTo));
// Result: ["Apple", "Banana", null, null]
Reference: Java Functional Programming & Streams API
Q 17

What is the difference between Stream.dropWhile() and Stream.filter()?

Hard
Answer
filter(Predicate) inspects EVERY element in the stream and keeps only matching elements. dropWhile(Predicate) drops elements sequentially ONLY until the first element fails the predicate; once a failure occurs, ALL remaining elements are passed through without evaluating the predicate again.
Explanation
dropWhile() operates based on contiguous initial sequence prefix rather than examining the entire stream.
Code Example Java
List<Integer> list = List.of(2, 4, 5, 8, 10, 11);
// Drops 2, 4. As soon as 5 fails predicate, rest (8, 10, 11) are included:
List<Integer> res = list.stream().dropWhile(n -> n % 2 == 0).toList(); // [5, 8, 10, 11]
Reference: Java Functional Programming & Streams API
Q 18

What is BiConsumer<T, U> and where is it commonly used in Java?

Easy
Answer
BiConsumer<T, U> represents an operation that accepts two input arguments and returns no result (void accept(T t, U u)). It is widely used in Map.forEach((k, v) -> ...), stream custom collectors (accumulator step), and asynchronous completion handlers.
Explanation
Map.forEach(BiConsumer) provides clean iteration over key-value pairs without iterating map.entrySet().
Code Example Java
Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 88);
// BiConsumer iteration over Map entries:
scores.forEach((name, score) -> System.out.println(name + " scored: " + score));
Reference: Java Functional Programming & Streams API
Q 19

What is the Diamond Operator with anonymous classes and lambdas in modern Java?

Medium
Answer
Java 9 enabled the diamond operator (<>) to be used with anonymous inner classes if the inferred type is denotable. Lambdas infer generic parameter types automatically from target functional interface contexts without explicit type declarations.
Explanation
Explicit lambda parameter types (e.g. (String s) -> s.length()) are optional and generally omitted for cleaner code ((s) -> s.length()).
Code Example Java
// Diamond with anonymous inner class (Java 9+):
Consumer<List<String>> consumer = new Consumer<>() {
    public void accept(List<String> list) { list.clear(); }
};
Reference: Java Functional Programming & Streams API
Q 20

What is Collectors.filtering() and Collectors.flatMapping() introduced in Java 9?

Hard
Answer
They are downstream collectors for multi-level groupingBy. Unlike stream.filter() (which removes entries before grouping), Collectors.filtering() applies filters inside individual bucket groups, preserving map keys with empty lists.
Explanation
Collectors.flatMapping() flattens nested collections inside downstream groupingBy operations.
Code Example Java
record Employee(String dept, int salary) {}
List<Employee> list = getEmployees();
// Retains all department keys, even if no employee has salary > 5000:
Map<String, Set<Employee>> result = list.stream().collect(
    Collectors.groupingBy(Employee::dept, Collectors.filtering(e -> e.salary() > 5000, Collectors.toSet()))
);
Reference: Java Functional Programming & Streams API
Q 21

What is the difference between flatMapToInt() and flatMap() in Java Streams?

Medium
Answer
flatMap() flattens element streams into a generic Object Stream<R>. flatMapToInt() flattens element streams into a primitive IntStream, allowing immediate access to primitive numeric aggregates like sum() and average().
Explanation
Equivalent primitive flattening methods include flatMapToLong() and flatMapToDouble().
Code Example Java
record Order(List<Integer> itemPrices) {}
List<Order> orders = getOrders();
// flatMapToInt flattens directly to IntStream:
int grandTotal = orders.stream()
    .flatMapToInt(o -> o.itemPrices().stream().mapToInt(Integer::intValue))
    .sum();
Reference: Java Functional Programming & Streams API
Q 22

What is java.util.OptionalDouble, OptionalInt, and OptionalLong?

Easy
Answer
They are primitive specializations of Optional for double, int, and long values. They eliminate wrapper object allocation and autoboxing overhead when working with primitive streams.
Explanation
They use getAsInt(), getAsLong(), or getAsDouble() instead of generic get() to return primitive types directly.
Code Example Java
IntStream stream = IntStream.of(10, 20, 30);
OptionalDouble avg = stream.average();
if (avg.isPresent()) {
    System.out.println("Average: " + avg.getAsDouble());
}
Reference: Java Functional Programming & Streams API
Q 23

What is the difference between Stream.findFirst() and Stream.min() / max()?

Medium
Answer
findFirst() is a short-circuiting operation that returns the first encounter-ordered element without inspecting subsequent elements. min() and max() are reduction operations that must inspect ALL elements across the entire stream using a Comparator to determine the minimum or maximum value.
Explanation
min() and max() return Optional<T> which will be empty if the stream contains zero elements.
Code Example Java
List<Integer> list = List.of(5, 3, 9, 1);
Optional<Integer> first = list.stream().findFirst(); // Returns 5 (short-circuits)
Optional<Integer> min = list.stream().min(Integer::compareTo); // Returns 1 (traverses all)
Reference: Java Functional Programming & Streams API
Q 24

How does Stream.collect(Collectors.summarizingInt()) work?

Medium
Answer
Collectors.summarizingInt() calculates count, sum, min, average, and max of integer stream elements in a single pass, returning an IntSummaryStatistics statistical object.
Explanation
summarizingLong() and summarizingDouble() provide equivalent summary statistics for long and double streams.
Code Example Java
List<String> words = List.of("apple", "banana", "kiwi");
IntSummaryStatistics stats = words.stream()
    .collect(Collectors.summarizingInt(String::length));

System.out.println("Count: " + stats.getCount());
System.out.println("Max: " + stats.getMax());
System.out.println("Avg: " + stats.getAverage());
Reference: Java Functional Programming & Streams API
Q 25

What is the difference between default methods and static methods in Java 8 interfaces?

Easy
Answer
Default methods provide an instance-level implementation that can be inherited and overridden by implementing classes. Static methods belong to the interface class itself, cannot be overridden, and must be invoked using InterfaceName.staticMethodName().
Explanation
Default methods enabled adding new methods (like Collection.stream() and forEach()) to existing core interfaces without breaking legacy classes that implemented them.
Code Example Java
interface MathService {
    static double add(double a, double b) { return a + b; }
    default double multiply(double a, double b) { return a * b; }
}
// Static: MathService.add(5, 3)
// Default: new MyService().multiply(5, 3)
Reference: Java Functional Programming & Streams API
Q 26

What is the Stream.close() method and when is it necessary to close a Stream?

Medium
Answer
Streams implement AutoCloseable. Closing a stream is necessary ONLY when the stream is backed by an I/O resource (e.g. Files.lines(Path), Files.walk(Path), or Files.list(Path)) to release underlying OS file handles. Memory-backed collection streams do not need closing.
Explanation
Always wrap I/O-backed streams inside a Try-With-Resources statement to guarantee resource release.
Code Example Java
// Required: I/O backed stream must be closed via try-with-resources
try (Stream<String> lines = Files.lines(Path.of("data.txt"))) {
    lines.filter(s -> s.contains("ERROR"))
         .forEach(System.out::println);
}
Reference: Java Functional Programming & Streams API
Q 27

What is the difference between Stream.unordered() and ordered streams?

Medium
Answer
By default, streams derived from List or ordered sources maintain an encounter order. Calling unordered() removes the encounter order constraint, allowing parallel operations (like distinct(), limit(), or groupingByConcurrent()) to execute significantly faster without maintaining ordering buffers.
Explanation
unordered() does not actively shuffle elements; it simply permits the JVM execution engine to ignore encounter ordering constraints.
Code Example Java
List<Integer> list = List.of(1, 2, 3, 4, 5, 2, 3);
// Eliminates ordering constraints for faster parallel deduplication:
List<Integer> unique = list.parallelStream()
    .unordered()
    .distinct()
    .toList();
Reference: Java Functional Programming & Streams API
Q 28

What is the difference between Stream.findFirst() and Stream.findAny() in sequential vs parallel streams?

Medium
Answer
In sequential streams, both typically return the first element in encounter order. In parallel streams, findFirst() forces thread synchronization to guarantee encounter order, while findAny() allows any thread to return its matching result immediately, maximizing parallel throughput.
Explanation
Use findAny() in parallel streams when any matching element satisfies the business requirement.
Code Example Java
List<String> items = List.of("alpha", "beta", "gamma");
// Optimized for parallel execution:
Optional<String> match = items.parallelStream()
    .filter(s -> s.contains("a"))
    .findAny();
Reference: Java Functional Programming & Streams API
Q 29

Why is Stream.count() optimized in Java 9+ for SIZED collections without traversing elements?

Medium
Answer
In Java 9+, if a stream pipeline consists solely of intermediate operations that do not alter stream size (such as map() or sorted()), the Stream implementation queries the underlying collection's known Spliterator.SIZED characteristic directly, returning size() in O(1) time without executing the pipeline steps.
Explanation
Because element processing is skipped, side-effects placed in peek() or map() before count() will NOT execute in optimized SIZED streams.
Code Example Java
List<String> list = List.of("a", "b", "c");
// Fast O(1) count without invoking toUpperCase() transformation:
long count = list.stream().map(String::toUpperCase).count();
Reference: Java Functional Programming & Streams API
Q 30

What is Memoization in functional Java and how is it implemented using computeIfAbsent?

Hard
Answer
Memoization is an optimization technique where expensive function results are cached by their input arguments so subsequent invocations with identical parameters return the cached value instantly. It is implemented cleanly in Java using Map.computeIfAbsent().
Explanation
Memoization converts exponential time recursive algorithms (like Fibonacci) into linear O(n) time.
Code Example Java
class Memoizer {
    private static final Map<Integer, Long> cache = new ConcurrentHashMap<>();
    public static long fib(int n) {
        if (n <= 1) return n;
        return cache.computeIfAbsent(n, key -> fib(key - 1) + fib(key - 2));
    }
}
Reference: Java Functional Programming & Streams API

About This Topic

Prepare for Java 8+ interviews with important concepts and commonly asked questions.