Practice commonly asked
Java 8+ interview questions with clear answers and explanations.
30
Interview Questions
Questions & Answers
Java 8+ Interview Questions
30 Questions
Q 31
What is the difference between Stream.iterator() and Spliterator in Streams?
Hard
Answer
iterator() traverses stream elements sequentially one by one. Spliterator (Splittable Iterator) can partition elements into sub-spliterators via trySplit() for parallel execution, estimating size (estimateSize()), and reporting traversal characteristics.
Explanation
Custom data structures implement Spliterator to participate in Java parallel streams.
Code Example
Java
List<String> list = List.of("A", "B", "C", "D");
Spliterator<String> s1 = list.spliterator();
Spliterator<String> s2 = s1.trySplit(); // Splits first partition into s2
Reference:
Java Functional Programming & Streams API
Q 32
What are Pure Functions and Referential Transparency in Java Functional Programming?
Hard
Answer
A Pure Function is a function whose return value depends exclusively on its input parameters and produces no observable side effects (no mutating external state, I/O, or global variables). Referential Transparency means a function call can be replaced with its resulting value without changing program behavior.
Explanation
Pure functions make concurrent and parallel stream processing safe and deterministic without synchronization locks.
Code Example
Java
// Pure function (Stateless, deterministic):
int add(int a, int b) { return a + b; }
// Impure function (Side effect, non-deterministic):
int total = 0;
int addWithSideEffect(int a) { total += a; return total; }
Reference:
Java Functional Programming & Streams API
Q 33
What is the Collector interface and what are its five core methods?
Hard
Answer
The Collector<T, A, R> interface defines custom reduction strategies. Its five methods are: 1. supplier() (creates mutable accumulator container), 2. accumulator() (folds element into container), 3. combiner() (merges two containers for parallel streams), 4. finisher() (transforms container to final result R), and 5. characteristics() (returns optimization flags).
Explanation
Collector Characteristics flags include IDENTITY_FINISH, CONCURRENT, and UNORDERED.
Reference:
Java Functional Programming & Streams API
Q 34
What is the difference between Stream.boxed() and mapToObj()?
Easy
Answer
boxed() converts a primitive stream (IntStream, LongStream, DoubleStream) into a boxed wrapper stream (Stream<Integer>, Stream<Long>, Stream<Double>). mapToObj(IntFunction) converts primitive values into arbitrary object types of any class.
Explanation
boxed() is equivalent to mapToObj(Integer::valueOf).
Reference:
Java Functional Programming & Streams API
Q 35
What is Collectors.collectingAndThen() and when is it used with unmodifiable structures?
Medium
Answer
Collectors.collectingAndThen() adapts a collector to perform an additional finishing transformation on its intermediate collected result. It is commonly used to produce immutable collections or extract specific calculated properties.
Explanation
It guarantees that the resulting collection cannot be mutated by caller code.
Reference:
Java Functional Programming & Streams API
Q 36
What is Higher-Order Function in Java?
Medium
Answer
A Higher-Order Function is a function or method that accepts one or more functions as arguments, returns a function as its result, or both.
Explanation
Standard library methods like Stream.filter(Predicate) and Comparator.comparing(Function) are prime examples of higher-order functions.
Code Example
Java
// Higher-order method returning a function configured by threshold:
public static Predicate<Integer> isGreaterThan(int threshold) {
return val -> val > threshold;
}
// Usage:
List<Integer> list = List.of(5, 10, 15, 20);
List<Integer> gt12 = list.stream().filter(isGreaterThan(12)).toList();
Reference:
Java Functional Programming & Streams API
Q 37
What is Currying and Partial Application in Java Functional Programming?
Hard
Answer
Currying is the functional programming technique of transforming a multi-argument function into a sequence of single-argument functions (Function<T, Function<U, R>>). Partial application fixes one or more arguments to produce a new function with fewer parameters.
// Curried Function: (a) -> (b) -> a + b
Function<Integer, Function<Integer, Integer>> addCurried = a -> b -> a + b;
Function<Integer, Integer> addTen = addCurried.apply(10); // Partially applied
System.out.println(addTen.apply(5)); // 15
Reference:
Java Functional Programming & Streams API
Q 38
What is Optional.stream() introduced in Java 9?
Medium
Answer
Optional.stream() converts an Optional instance into a Stream of either one element (if present) or zero elements (if empty). It allows seamless filtering and unwrapping of Optional elements inside a Stream pipeline using flatMap().
Explanation
It eliminates the need for .filter(Optional::isPresent).map(Optional::get) boilerplate.
Reference:
Java Functional Programming & Streams API
Q 39
What is Optional.ifPresentOrElse() introduced in Java 9?
Easy
Answer
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) executes the consumer action with the present value if present, or executes the emptyAction runnable if the Optional is empty.
Explanation
It replaces clumsy if-else checks on optional.isPresent() with a clean functional declaration.
Code Example
Java
Optional<String> opt = findUsername();
opt.ifPresentOrElse(
name -> System.out.println("Found: " + name),
() -> System.out.println("User not found")
);
Reference:
Java Functional Programming & Streams API
Q 40
How does Optional.flatMap() differ from Optional.map()?
Medium
Answer
map() wraps the transformed result in an Optional (e.g. if the mapping function returns Optional<U>, map() results in Optional<Optional<U>>). flatMap() flattens the nested structure, requiring the mapping function to return Optional<U> directly and unwrapping the outer layer.
Explanation
Use flatMap() when chaining methods that themselves return Optional values to prevent deeply nested Optional types.
Code Example
Java
record Address(String zipCode) {}
record User(Optional<Address> address) {}
Optional<User> userOpt = Optional.of(new User(Optional.of(new Address("10001"))));
// flatMap extracts inner optional cleanly:
Optional<String> zip = userOpt
.flatMap(User::address)
.map(Address::zipCode);
Reference:
Java Functional Programming & Streams API
Q 41
What is the difference between Function.identity() and lambda expression x -> x?
Easy
Answer
Function.identity() returns a cached singleton instance of an identity function. The lambda x -> x generates a new functional interface instance (or synthetic method call) and is functionally identical but less explicit in intent.
Explanation
Function.identity() is widely used in Collectors.toMap() or groupingBy() when mapping an element directly to itself.
Reference:
Java Functional Programming & Streams API
Q 43
What is the difference between anyMatch(), allMatch(), and noneMatch() in Java Streams?
Easy
Answer
anyMatch(Predicate) returns true if at least one element matches. allMatch(Predicate) returns true only if all elements match (returns true on empty streams - vacuous truth). noneMatch(Predicate) returns true if no elements match.
Explanation
All three are short-circuiting terminal operations that terminate evaluation as soon as the definitive boolean result is known.
Reference:
Java Functional Programming & Streams API
Q 44
What is Stream.skip() vs Stream.limit() and how do they implement pagination?
Easy
Answer
skip(n) is a stateful intermediate operation that discards the first n elements. limit(maxSize) is a short-circuiting intermediate operation that truncates the stream to at most maxSize elements. Together, stream.skip(page * size).limit(size) implements pagination.
Explanation
On sorted or ordered streams, skip(n) must still traverse the first n elements, so database-level pagination is preferred for massive datasets.
Reference:
Java Functional Programming & Streams API
Q 45
What is Collectors.joining() and what parameters does it accept?
Easy
Answer
Collectors.joining() concatenates elements of a CharSequence stream into a single String. It has three overloaded forms: joining() (no delimiter), joining(delimiter), and joining(delimiter, prefix, suffix).
Explanation
It is backed internally by StringBuilder or StringJoiner for efficient, single-pass string concatenation.
Code Example
Java
List<String> languages = List.of("Java", "Kotlin", "Scala");
String result = languages.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(result); // [Java, Kotlin, Scala]
Reference:
Java Functional Programming & Streams API
Q 46
What is Collectors.toMap() and how do you handle key collision conflicts?
Medium
Answer
Collectors.toMap(keyMapper, valueMapper) converts a stream to a Map. If duplicate keys occur, it throws IllegalStateException by default. A 3-argument version accepting a BinaryOperator mergeFunction resolves key collisions by defining which value to retain or merge.
Explanation
A 4-argument version also accepts a mapSupplier (e.g. TreeMap::new) to control the concrete Map implementation returned.
Reference:
Java Functional Programming & Streams API
Q 47
What is the difference between flatMap() and mapMulti() introduced in Java 16?
Hard
Answer
flatMap() requires creating a new intermediate Stream object for every single element, which creates substantial object allocation overhead. mapMulti() uses an imperative consumer callback pattern to push multiple elements directly into the downstream pipeline without creating intermediate stream instances.
Explanation
mapMulti() is significantly faster and more memory-efficient when mapping an element to zero or a small number of elements.
Code Example
Java
List<Integer> numbers = List.of(1, 2, 3);
// mapMulti pushes 0 or more elements directly to downstream consumer:
List<Integer> doubled = numbers.stream()
.<Integer>mapMulti((num, consumer) -> {
consumer.accept(num);
consumer.accept(num * 10);
})
.toList(); // [1, 10, 2, 20, 3, 30]
Reference:
Java Functional Programming & Streams API
Q 48
How does Stream.sorted() work for custom objects without Comparable implementation?
Medium
Answer
If objects do not implement Comparable, calling no-arg sorted() throws a ClassCastException at runtime. To sort custom objects, pass an explicit Comparator instance (such as Comparator.comparing()) to sorted(Comparator).
Explanation
Comparator methods like thenComparing() and reversed() allow readable, chained multi-field sorting.
Code Example
Java
record User(String name, int age) {}
List<User> users = getUsers();
// Sort by age ascending, then by name descending:
List<User> sorted = users.stream()
.sorted(Comparator.comparing(User::age).thenComparing(Comparator.comparing(User::name).reversed()))
.collect(Collectors.toList());
Reference:
Java Functional Programming & Streams API
Q 49
What is the difference between mapToInt() and map() in Java Streams?
Easy
Answer
map() returns a generic object Stream<R> (e.g. Stream<Integer>), causing boxing/unboxing overhead for primitives. mapToInt() returns a primitive IntStream, eliminating wrapper objects and providing built-in numeric terminal operations like sum(), average(), and max().
Explanation
Use mapToInt(), mapToLong(), or mapToDouble() whenever performing mathematical or aggregate calculations on collections.
Code Example
Java
List<String> words = List.of("apple", "banana", "cherry");
// mapToInt produces IntStream with direct sum() method:
int totalChars = words.stream().mapToInt(String::length).sum();
Reference:
Java Functional Programming & Streams API
Q 50
What is the difference between UnaryOperator<T> and BinaryOperator<T>?
Easy
Answer
UnaryOperator<T> extends Function<T, T> and takes a single operand of type T returning a result of the same type T. BinaryOperator<T> extends BiFunction<T, T, T> and takes two operands of type T returning a result of type T.
Explanation
They are specialized functional interfaces used when input and output types are identical, simplifying function signatures.
Code Example
Java
UnaryOperator<String> toUpper = String::toUpperCase;
BinaryOperator<Integer> multiply = (a, b) -> a * b;
System.out.println(toUpper.apply("java")); // JAVA
System.out.println(multiply.apply(4, 5)); // 20
Reference:
Java Functional Programming & Streams API
Q 51
What is Stream.toList() in Java 16+ vs Collectors.toList() and Collectors.toUnmodifiableList()?
Medium
Answer
Stream.toList() (Java 16+) directly returns an unmodifiable List instance with a compact, allocation-optimized implementation, allowing null elements. Collectors.toList() returns a mutable implementation (typically ArrayList). Collectors.toUnmodifiableList() returns an unmodifiable list that strictly disallows nulls.
Explanation
Stream.toList() is shorter, faster, and produces less garbage collector allocations than stream.collect(Collectors.toList()).
Code Example
Java
List<String> source = List.of("Apple", "Banana");
// Modern concise unmodifiable collection (Java 16+):
List<String> directList = source.stream()
.filter(s -> s.startsWith("A"))
.toList(); // Direct unmodifiable list
Reference:
Java Functional Programming & Streams API
Q 52
What is Stream.concat() and what is the maximum depth precaution when chaining streams?
Hard
Answer
Stream.concat(a, b) creates a lazily concatenated stream whose elements are all elements of the first stream followed by the second. Chaining concat() recursively in deep hierarchies can lead to deep call stacks and StackOverflowError during traversal.
Explanation
To combine multiple streams safely without deep recursion, collect individual streams into a Stream of Streams and use flatMap(Function.identity()).
Reference:
Java Functional Programming & Streams API
Q 53
How does Stream.distinct() identify duplicate elements and what is its performance cost?
Medium
Answer
distinct() is a stateful intermediate operation that checks for element duplicates using equals() and hashCode() contracts. It maintains an internal HashSet of visited elements, incurring memory overhead and requiring full stream buffering in parallel executions.
Explanation
For custom object streams, ensure equals() and hashCode() are properly overridden, otherwise distinct() will compare object identity instead of value equality.
Reference:
Java Functional Programming & Streams API
Q 54
What is the difference between Function.compose() and Function.andThen()?
Easy
Answer
Both compose two functions into a single pipeline: andThen(after) executes the current function first and passes its result to 'after' (f(x) then g(y)). compose(before) executes 'before' first and passes its result to the current function (g(x) then f(y)).
Explanation
f.andThen(g).apply(x) is equivalent to g(f(x)). f.compose(g).apply(x) is equivalent to f(g(x)).
Code Example
Java
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add3 = x -> x + 3;
// (5 * 2) + 3 = 13
int res1 = multiplyBy2.andThen(add3).apply(5);
// (5 + 3) * 2 = 16
int res2 = multiplyBy2.compose(add3).apply(5);
Reference:
Java Functional Programming & Streams API
Q 55
Why should stream pipelines avoid modifying shared external state (Side Effects)?
Medium
Answer
Modifying external shared variables inside stream lambdas introduces side effects, breaking functional purity and causing unpredictable race conditions and corrupt data structures when executed in parallel streams.
Explanation
Never use forEach() to add elements into external lists; use Collectors.toList() instead to collect results safely and functionally.
Code Example
Java
// ANTI-PATTERN (Side effect bug):
// List<String> result = new ArrayList<>();
// stream.parallel().filter(...).forEach(result::add); // Race condition!
// CORRECT (Pure Functional):
List<String> result = stream.parallel().filter(...).collect(Collectors.toList());
Reference:
Java Functional Programming & Streams API
Q 56
What is Stream.collect(Collectors.collectingAndThen())?
Medium
Answer
collectingAndThen() is a specialized collector that performs a downstream collection and then applies an additional finishing transformation function to the final collected result.
Explanation
It is frequently used to make collected lists or maps unmodifiable (Collections.unmodifiableList) or extract specific properties from collected collections.
Reference:
Java Functional Programming & Streams API
Q 57
How do you create Infinite Streams using Stream.generate() and Stream.iterate()?
Medium
Answer
Stream.generate(Supplier<T>) produces an unbounded stream by continuously invoking the supplier. Stream.iterate(seed, UnaryOperator) generates an infinite sequential stream by repeatedly applying the operator to the previous element.
Explanation
Infinite streams MUST be bounded using short-circuiting operations like limit() or takeWhile() before terminal operations are called to avoid infinite loops.
Code Example
Java
// Fibonacci sequence using Stream.iterate:
Stream.iterate(new int[]{0, 1}, f -> new int[]{f[1], f[0] + f[1]})
.limit(10)
.map(f -> f[0])
.forEach(System.out::println);
Reference:
Java Functional Programming & Streams API
Q 58
What is Collectors.teeing() introduced in Java 12?
Hard
Answer
Collectors.teeing() composites two downstream collectors into a single collector pass over a stream, feeding every element to both collectors and then merging their two results using a BiFunction.
Explanation
teeing() allows calculating two distinct statistics (such as min and max, or sum and count for average) simultaneously in a single stream traversal.
Code Example
Java
record MinMax(int min, int max) {}
List<Integer> list = List.of(5, 2, 9, 1, 7);
// Computes min and max in one pass:
MinMax result = list.stream().collect(Collectors.teeing(
Collectors.minBy(Integer::compareTo),
Collectors.maxBy(Integer::compareTo),
(min, max) -> new MinMax(min.orElse(0), max.orElse(0))
));
Reference:
Java Functional Programming & Streams API
Q 59
What is the difference between Stream.peek() and Stream.forEach()?
Easy
Answer
peek() is an intermediate operation designed strictly for debugging elements as they flow through the pipeline, returning a Stream<T>. forEach() is a terminal operation that consumes the stream and produces a void result.
Explanation
In Java 9+, the JVM may optimize away peek() calls completely if the terminal operation (like count()) does not require inspecting element values.
Reference:
Java Functional Programming & Streams API
Q 60
What is variable capture and effectively final in Java Lambda expressions?
Medium
Answer
Lambdas can capture (read) local variables from their enclosing lexical scope only if the variable is declared 'final' or is 'effectively final' (its value is never modified after initialization). Lambdas cannot modify captured local variables.
Explanation
This restriction exists because local variables reside on the stack; allowing lambdas to mutate stack variables in other threads would cause race conditions and stack frame lifecycle corruption.
Code Example
Java
int baseRate = 10; // Effectively final
// baseRate = 20; // Reassigning breaks effectively final and causes compilation error!
Function<Integer, Integer> calc = x -> x * baseRate; // Valid variable capture
Reference:
Java Functional Programming & Streams API
About This Topic
Prepare for
Java 8+ interviews with important concepts
and commonly asked questions.