Java 8+

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

15 Interview Questions

Java 8+ Interview Questions

15 Questions
Q 61

How do Parallel Streams work internally and what are the risks of using them?

Hard
Answer
Parallel streams partition stream elements using Spliterators and process chunks concurrently across CPU cores using the shared ForkJoinPool.commonPool(). Risks include: blocking I/O starving the shared pool, race conditions on non-thread-safe state, and overhead exceeding benefits on small datasets.
Explanation
Parallel streams should only be used on CPU-bound computations with large datasets ($N \times Q > 10,000$) where operations are completely stateless and associative.
Code Example Java
// Fast for CPU-bound computations:
long count = largeNumberList.parallelStream()
    .filter(PrimeChecker::isPrime)
    .count();
Reference: Java Functional Programming & Streams API
Q 62

What is the difference between Stream.ofNullable(), takeWhile(), and dropWhile() in Java 9+?

Hard
Answer
Stream.ofNullable() creates a single-element stream if non-null, or an empty stream if null. takeWhile(Predicate) takes elements from a sorted stream as long as the predicate matches and terminates on the first mismatch. dropWhile(Predicate) drops elements until the first mismatch and includes the rest.
Explanation
Unlike filter() which inspects all elements across the entire stream, takeWhile() short-circuits as soon as a condition fails in ordered streams.
Code Example Java
List<Integer> list = List.of(2, 4, 6, 7, 8, 10);
// Stops as soon as odd number 7 is encountered:
List<Integer> evens = list.stream()
    .takeWhile(n -> n % 2 == 0)
    .collect(Collectors.toList()); // [2, 4, 6]
Reference: Java Functional Programming & Streams API
Q 63

What are Primitive Streams (IntStream, LongStream, DoubleStream) and why are they important?

Medium
Answer
Primitive streams are specialized stream implementations for int, long, and double primitives. They eliminate the severe memory and performance overhead of continuous boxing and unboxing (e.g. Integer <-> int) and provide specialized aggregate methods (sum(), average(), summaryStatistics(), range()).
Explanation
Converting Stream<Integer> to IntStream via mapToInt() drastically increases performance in numeric calculations.
Code Example Java
// IntStream eliminates wrapper objects and provides range + sum:
int total = IntStream.rangeClosed(1, 100).sum(); // 5050

IntSummaryStatistics stats = IntStream.of(10, 20, 30).summaryStatistics();
System.out.println("Avg: " + stats.getAverage() + ", Max: " + stats.getMax());
Reference: Java Functional Programming & Streams API
Q 64

What is the difference between Optional.orElse() and Optional.orElseGet()?

Medium
Answer
orElse(defaultValue) evaluates the default argument eagerly even when the Optional contains a present value. orElseGet(Supplier<? extends T>) evaluates the supplier lazily ONLY when the Optional is empty.
Explanation
Always prefer orElseGet() if creating the default fallback value involves expensive computations, database calls, or object allocations.
Code Example Java
Optional<String> opt = Optional.of("Real Value");
// orElse runs computeDefault() even though value is present!
String v1 = opt.orElse(computeDefault());
// orElseGet avoids running lambda because value is present:
String v2 = opt.orElseGet(() -> computeDefault());
Reference: Java Functional Programming & Streams API
Q 65

What is Optional<T> in Java 8 and how does it prevent NullPointerExceptions?

Easy
Answer
Optional<T> is a container object that may or may not contain a non-null value. It provides a type-level representation of missing values, requiring callers to explicitly handle presence or absence using methods like map(), ifPresent(), orElse(), or orElseGet().
Explanation
Optional should be used primarily as method return types to communicate potential absence. Avoid using Optional as class fields, method arguments, or inside collections.
Code Example Java
public Optional<User> findUserById(int id) {
    User user = database.find(id);
    return Optional.ofNullable(user);
}
// Safe usage:
String name = findUserById(10)
    .map(User::getName)
    .orElse("Default User");
Reference: Java Functional Programming & Streams API
Q 66

What is the difference between Collectors.groupingBy() and Collectors.partitioningBy()?

Medium
Answer
groupingBy() groups elements into arbitrary keys based on a Function<T, K>, returning a Map<K, List<T>>. partitioningBy() groups elements strictly into a boolean binary partition based on a Predicate<T>, always returning a Map<Boolean, List<T>> containing both true and false keys.
Explanation
partitioningBy() is more memory-efficient when dividing data into exactly two groups (e.g., pass/fail, even/odd, active/inactive).
Code Example Java
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
// Partitions into true (even) and false (odd) keys:
Map<Boolean, List<Integer>> evenOddMap = numbers.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));
Reference: Java Functional Programming & Streams API
Q 67

How does Collectors.groupingBy() work and how do you perform multi-level or downstream aggregations?

Hard
Answer
Collectors.groupingBy() partitions stream elements by a classification function into a Map<K, List<V>>. It supports downstream collectors (e.g. counting(), mapping(), summingInt(), toSet()) to perform secondary grouping or aggregations on bucket items.
Explanation
Downstream collectors can be chained to build complex multi-level maps like Map<Department, Set<String>> or Map<Category, Double>.
Code Example Java
record Employee(String dept, String name, int salary) {}
List<Employee> list = getEmployees();
// Group by dept and calculate average salary per department:
Map<String, Double> avgSalaryByDept = list.stream()
    .collect(Collectors.groupingBy(
        Employee::dept,
        Collectors.averagingInt(Employee::salary)
    ));
Reference: Java Functional Programming & Streams API
Q 68

How does Stream.reduce() work and what are its accumulator and combiner functions?

Hard
Answer
reduce() is a terminal operation that aggregates stream elements into a single summary value. It takes an identity value (initial seed), an accumulator (combines element with running subtotal), and a combiner (combines intermediate results across threads in parallel streams).
Explanation
The accumulator and combiner operations must be associative and stateless to guarantee deterministic results in parallel stream execution.
Code Example Java
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
// 2-argument reduce: identity + accumulator
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
// Equivalent method reference:
int sumRef = numbers.stream().reduce(0, Integer::sum);
Reference: Java Functional Programming & Streams API
Q 69

What is the difference between findFirst() and findAny() in Java Streams?

Medium
Answer
findFirst() returns the first element in strict encounter order. findAny() returns any matching element nondeterministically, which provides significantly higher throughput in parallel streams because worker threads do not need to coordinate encounter ordering.
Explanation
In sequential streams, findAny() typically returns the first element, but in parallel streams, it returns the result from whichever thread finishes first.
Code Example Java
List<Integer> list = List.of(1, 2, 3, 4, 5);
// In parallel stream, findAny is faster and non-deterministic:
Optional<Integer> any = list.parallelStream().filter(n -> n > 2).findAny();
// findFirst strictly preserves encounter order:
Optional<Integer> first = list.parallelStream().filter(n -> n > 2).findFirst();
Reference: Java Functional Programming & Streams API
Q 70

How does Lazy Evaluation work in Java Streams and what is Short-Circuiting?

Medium
Answer
Lazy evaluation means intermediate operations are not executed until the terminal operation is called, allowing the pipeline to process elements in a single pass. Short-circuiting operations (e.g., findFirst, anyMatch, limit) terminate evaluation as soon as the result is determined without processing remaining elements.
Explanation
Short-circuiting operations allow streams to process infinite streams safely and terminate early.
Code Example Java
List<String> list = List.of("Apple", "Banana", "Avocado");
String first = list.stream()
    .filter(s -> s.startsWith("A")) // Evaluated only until first match found
    .findFirst() // Short-circuiting terminal operation
    .orElse("None");
Reference: Java Functional Programming & Streams API
Q 71

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

Medium
Answer
map() applies a 1-to-1 transformation function on each element and wraps the result (Stream<T> -> Stream<R>). flatMap() applies a 1-to-many transformation where each element produces a stream, and then flattens the resulting streams into a single consolidated stream (Stream<List<T>> -> Stream<T>).
Explanation
Use map() for simple value mapping. Use flatMap() to flatten nested collections, arrays, or Optional values into a single stream.
Code Example Java
List<List<String>> nested = List.of(
    List.of("A", "B"),
    List.of("C", "D")
);
// flatMap flattens Stream<List<String>> into Stream<String>:
List<String> flat = nested.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList()); // ["A", "B", "C", "D"]
Reference: Java Functional Programming & Streams API
Q 72

What is the difference between Intermediate Operations and Terminal Operations in Java Streams?

Easy
Answer
Intermediate operations (e.g. filter, map, flatMap, sorted) transform a stream into another stream and are evaluated lazily. Terminal operations (e.g. collect, forEach, reduce, count) trigger the execution of the stream pipeline and produce a non-stream result or side-effect.
Explanation
A stream pipeline will not perform any processing until a terminal operation is invoked on it (Lazy Evaluation). Once a terminal operation completes, the stream is consumed and cannot be reused.
Code Example Java
List<String> names = List.of("Anna", "Bob", "Alex");
// filter and map are lazy intermediate operations;
// collect is the terminal operation that triggers processing:
List<String> result = names.stream()
    .filter(s -> s.startsWith("A"))
    .map(String::toUpperCase)
    .collect(Collectors.toList());
Reference: Java Functional Programming & Streams API
Q 73

What are the four types of Method References in Java 8?

Medium
Answer
1. Reference to a static method (ContainingClass::staticMethodName), 2. Reference to an instance method of a particular object (containingObject::instanceMethodName), 3. Reference to an instance method of an arbitrary object of a particular type (ContainingType::methodName), and 4. Reference to a constructor (ClassName::new).
Explanation
Method references provide a more concise and readable shorthand syntax for simple lambda expressions that only delegate to existing methods.
Code Example Java
// 1. Static: Math::max
// 2. Instance on specific object: System.out::println
// 3. Instance on arbitrary type: String::toUpperCase
// 4. Constructor: ArrayList::new
Function<String, String> upper = String::toUpperCase;
Supplier<List<String>> listSupplier = ArrayList::new;
Reference: Java Functional Programming & Streams API
Q 74

What are the four core built-in Functional Interfaces in java.util.function package?

Easy
Answer
The four core interfaces are: 1. Predicate<T> (boolean test(T t)), 2. Function<T, R> (R apply(T t)), 3. Consumer<T> (void accept(T t)), and 4. Supplier<T> (T get()).
Explanation
They also have two-argument variants (BiPredicate, BiFunction, BiConsumer) and primitive specializations (IntPredicate, LongSupplier) to eliminate autoboxing overhead.
Code Example Java
Predicate<String> isLong = s -> s.length() > 5;
Function<String, Integer> getLength = String::length;
Consumer<String> printer = System.out::println;
Supplier<Double> randomSupplier = Math::random;
Reference: Java Functional Programming & Streams API
Q 75

What is a Functional Interface in Java, and what is the role of the @FunctionalInterface annotation?

Easy
Answer
A Functional Interface is an interface with exactly one abstract method (Single Abstract Method or SAM). It can have any number of default or static methods. The @FunctionalInterface annotation is optional but recommended as it forces the compiler to verify SAM compliance.
Explanation
Functional interfaces act as target types for Lambda expressions and Method References in Java 8+.
Code Example Java
@FunctionalInterface
interface Transformer<T, R> {
    R transform(T input); // Single abstract method
    default void log(T input) { System.out.println("Transforming: " + input); }
}
Reference: Java Functional Programming & Streams API

About This Topic

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