Java

Java 8 Interview Questions

Test your Java 8+ knowledge with 20 interview questions covering Lambda expressions, Streams API, Optional, functional interfaces, method references, default methods, and the Date/Time API.

20 Questions
30 min
Mixed Difficulty
Start Quiz

Topics Covered

Lambda ExpressionsFunctional InterfacesStream OperationsOptionalMethod ReferencesDefault MethodsCollectorsDate/Time APIParallel Streams

Difficulty Breakdown

7
Junior
9
Mid-Level
4
Senior

What to Expect

  • Multiple choice questions with 4 options each
  • Instant score and topic-by-topic breakdown
  • Detailed explanations for every question
  • Personalized course recommendations based on your weak areas

Java 8 Interview Questions and Answers

Below are all 20 questions covered in this quiz, grouped by topic. Each question includes the correct answer and a detailed explanation to help you prepare for your next interview.

1Lambda Expressions

Q

What is a lambda expression in Java 8?

A

A concise way to represent an anonymous function that can be passed as an argument

A lambda expression is a concise representation of an anonymous function that can be passed around. It has parameters, an arrow token (->), and a body. For example: (a, b) -> a + b. Lambdas enable functional programming in Java and are primarily used to implement functional interfaces.

3Functional Interfaces

Q

Which of the following is a valid functional interface in Java 8?

A

An interface with exactly one abstract method

A functional interface is an interface with exactly one abstract method (SAM — Single Abstract Method). It can have multiple default or static methods. The @FunctionalInterface annotation is optional but recommended — it causes a compile error if the interface has more than one abstract method.

Q

What does the Predicate<T> functional interface represent?

A

A function that takes an argument and returns a boolean

Predicate<T> represents a boolean-valued function of one argument. Its abstract method is test(T t) which returns a boolean. It also provides default methods like and(), or(), and negate() for composing predicates. It is commonly used with Stream.filter().

Q

What is the difference between Function<T, R> and Consumer<T> in java.util.function?

A

Function takes an argument and returns a result; Consumer takes an argument and returns nothing (void)

Function<T, R> represents a function that accepts an argument of type T and produces a result of type R via its apply(T t) method. Consumer<T> represents an operation that accepts a single argument and returns no result via its accept(T t) method. Supplier<T> is the opposite of Consumer — it takes no arguments and returns a result.

5Stream Operations

Q

What is the output of the following code?

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
  .filter(n -> n.length() > 3)
  .map(String::toUpperCase)
  .forEach(System.out::println);
A

ALICE CHARLIE

The stream first filters names with length > 3, keeping "Alice" (5) and "Charlie" (7) while removing "Bob" (3). Then map(String::toUpperCase) converts them to uppercase. Finally, forEach prints each element. The result is ALICE and CHARLIE on separate lines.

Q

What is the difference between Stream.map() and Stream.flatMap()?

A

map() transforms each element to one element; flatMap() transforms each element to a stream and flattens the results

map() applies a one-to-one transformation: each input element produces exactly one output element. flatMap() applies a one-to-many transformation: each input element produces a stream of elements, and all resulting streams are flattened into a single stream. For example, if you have a List<List<String>>, flatMap() can flatten it into a Stream<String>.

Q

What is the result of the following code?

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
  .reduce(0, (a, b) -> a + b);
System.out.println(sum);
A

15

The reduce() operation combines all elements of the stream into a single result. Starting with the identity value 0, it applies (a, b) -> a + b cumulatively: 0+1=1, 1+2=3, 3+3=6, 6+4=10, 10+5=15. The final result is 15.

Q

What is the difference between Stream intermediate and terminal operations?

A

Intermediate operations are lazy and return a new Stream; terminal operations trigger processing and produce a result or side-effect

Intermediate operations (filter, map, flatMap, sorted, distinct, peek) are lazy — they don't process elements until a terminal operation is invoked. They return a new Stream allowing chaining. Terminal operations (collect, forEach, reduce, count, findFirst, anyMatch) trigger the actual processing pipeline and produce a non-stream result. A stream can have many intermediate operations but only one terminal operation.

Q

What is the output of the following code?

List<List<Integer>> nested = Arrays.asList(
  Arrays.asList(1, 2),
  Arrays.asList(3, 4),
  Arrays.asList(5, 6)
);
List<Integer> flat = nested.stream()
  .flatMap(Collection::stream)
  .filter(n -> n % 2 != 0)
  .collect(Collectors.toList());
System.out.println(flat);
A

[1, 3, 5]

First, flatMap(Collection::stream) flattens the nested lists into a single stream: 1, 2, 3, 4, 5, 6. Then filter(n -> n % 2 != 0) keeps only odd numbers: 1, 3, 5. Finally, collect(Collectors.toList()) gathers them into a list. The result is [1, 3, 5].

2Optional

Q

What is the correct way to create an Optional that may or may not contain a value?

A

Optional.ofNullable(value)

Optional.ofNullable(value) is the correct choice when the value might be null — it returns Optional.empty() for null and Optional.of(value) otherwise. Optional.of(value) throws NullPointerException if value is null. Optional has no public constructor, so new Optional() won't compile.

Q

What will the following code print?

Optional<String> opt = Optional.of("Hello");
String result = opt
  .filter(s -> s.length() > 10)
  .map(String::toUpperCase)
  .orElse("default");
System.out.println(result);
A

default

The Optional contains "Hello" (length 5). The filter checks if length > 10, which is false, so it returns Optional.empty(). The subsequent map() on an empty Optional does nothing. Finally, orElse("default") returns "default" since the Optional is empty.

1Method References

Q

Which of the following are valid method reference types in Java 8?

1. String::valueOf (static method)
2. String::length (instance method of an arbitrary object)
3. myStr::toUpperCase (instance method of a particular object)
4. ArrayList::new (constructor reference)
A

All four (1, 2, 3, and 4)

Java 8 supports four kinds of method references: (1) Reference to a static method (ClassName::staticMethod), (2) Reference to an instance method of an arbitrary object of a particular type (ClassName::instanceMethod), (3) Reference to an instance method of a particular object (object::instanceMethod), and (4) Reference to a constructor (ClassName::new).

2Default Methods

Q

What is the purpose of default methods in interfaces, introduced in Java 8?

A

To provide backward-compatible evolution of interfaces by adding methods with a default implementation

Default methods allow interfaces to provide method implementations using the 'default' keyword. The primary motivation was to evolve existing interfaces (like java.util.Collection) with new methods (like stream(), forEach()) without breaking all implementing classes. Interfaces still cannot hold instance state.

Q

What happens if a class implements two interfaces that both define the same default method?

A

The compiler throws an error unless the class overrides the method to resolve the conflict

When a class implements two interfaces with the same default method signature, there is a diamond problem. The compiler forces the class to override the conflicting method. Inside the override, you can explicitly call a specific interface's default using InterfaceName.super.methodName().

2Collectors

Q

What does Collectors.groupingBy() do in a stream pipeline?

Map<String, List<Employee>> result = employees.stream()
  .collect(Collectors.groupingBy(Employee::getDepartment));
A

Groups employees into a Map where the key is the department and the value is a list of employees in that department

Collectors.groupingBy() is a collector that groups stream elements by a classification function and returns a Map. The key is the classification result (department name) and the value is a List of elements sharing that key. You can pass a downstream collector as a second argument, e.g., Collectors.groupingBy(Employee::getDepartment, Collectors.counting()) to get counts per department.

Q

What does the Collector returned by Collectors.toUnmodifiableList() guarantee compared to Collectors.toList()?

A

It returns an immutable list that throws UnsupportedOperationException on modification attempts

Collectors.toUnmodifiableList() (introduced in Java 10, building on Java 8 patterns) returns a list that cannot be modified — any call to add(), remove(), or set() throws UnsupportedOperationException. It also does not permit null elements. Collectors.toList() returns a regular mutable ArrayList with no such guarantees.

2Date/Time API

Q

Which class should you use to represent a date without time or timezone in Java 8's Date/Time API?

A

java.time.LocalDate

LocalDate represents a date without time-of-day and without timezone (e.g., 2026-04-04). LocalDateTime includes time but no timezone. ZonedDateTime includes both time and timezone. java.util.Date is the legacy class that Java 8's java.time API was designed to replace.

Q

How do you convert between the legacy Date API and the Java 8 Date/Time API?

Date legacyDate = new Date();
// Convert to Java 8
A

legacyDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()

The bridge between legacy Date and Java 8's API is the Instant class. Date.toInstant() converts to an Instant, then atZone() attaches a timezone to get a ZonedDateTime, and toLocalDateTime() strips the timezone. Going the other way: Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()).

2Parallel Streams

Q

Why should you be cautious when using parallel streams?

A

They use the common ForkJoinPool, can introduce overhead for small datasets, and may cause issues with non-thread-safe operations or ordering-dependent logic

Parallel streams split work across multiple threads using the common ForkJoinPool. Risks include: (1) overhead from thread coordination can make small datasets slower, (2) shared mutable state can cause race conditions, (3) operations depending on encounter order may produce wrong results, (4) blocking operations can starve the shared pool affecting the entire application. Measure before parallelizing.

Q

What is the potential problem with this code?

Map<String, Long> wordCounts = words.parallelStream()
  .collect(Collectors.groupingBy(
    Function.identity(),
    HashMap::new,
    Collectors.counting()
  ));
A

Using HashMap::new as the map factory with a parallel stream can cause race conditions; use Collectors.groupingByConcurrent() or a ConcurrentHashMap factory instead

When using parallelStream(), the regular groupingBy() collector with a HashMap factory is not thread-safe. The parallel stream processes elements concurrently, and merging into a HashMap can cause data corruption or lost entries. The safe alternatives are: (1) use Collectors.groupingByConcurrent() which uses a ConcurrentHashMap internally, or (2) use a ConcurrentHashMap::new factory. groupingByConcurrent() is also more efficient for parallel streams because it allows concurrent accumulation instead of merging separate maps.

Ready to test yourself?

Take the interactive quiz and get your score with a personalized topic breakdown.

Start the Quiz

Your Career Transformation Starts Now

Join thousands of developers mastering in-demand skills with Amigoscode. Try it free today.