Skip to main content

50+ Real & Tricky Java 8 Interview Questions

This guide focuses on conceptual, tricky, and scenario-based questions commonly asked in Java 8+ interviews for developers with 2–7 years of experience.


🌊 Java Streams API

Basic Concepts

  • Why can a stream be consumed only once? A Java stream acts like a data pipeline. Once a terminal operation (like forEach, collect, or count) is executed, the stream processes the elements step-by-step and closes automatically. Reusing it will throw an IllegalStateException.
    Stream<String> names = Stream.of("Alice", "Bob", "Charlie");
    names.forEach(System.out::println); // Terminal operation
    // names.count(); // This would throw an IllegalStateException

* **Why do we need streams if we can write code without them?**
Streams make code shorter, cleaner, and more readable by adopting a declarative approach (describing *what* to do rather than *how* to do it). They also support functional programming and easy parallel processing.
* **Explain the stream pipeline structure.**
A stream pipeline consists of three main parts:
1. **Source:** Where data comes from (e.g., a List, Set, or Array).
2. **Intermediate Operations:** Transformations like `filter`, `map`, or `sorted`. These are lazy and just prepare the pipeline.
3. **Terminal Operation:** Operations like `forEach` or `collect` that trigger the execution and produce a result.


```java
List<String> activeUsers = users.stream() // 1. Source
.filter(User::isActive) // 2. Intermediate Operation
.map(User::getUsername) // 2. Intermediate Operation
.collect(Collectors.toList()); // 3. Terminal Operation

  • Why are intermediate operations called "lazy"? They do not run immediately when written. They simply prepare the pipeline, and the actual processing only begins when a terminal operation is called.
  • Why is a Stream not a Data Structure? Unlike Collections, Streams do not store data. They are simply a conduit to process data flowing from a source.

Execution & Performance

  • If a pipeline has multiple filters, does it iterate the collection multiple times? No. Intermediate operations are combined into a single pass. Java applies all filters to the first element before moving to the second, making it highly efficient.
  • How can you debug a stream without affecting the result? Use the peek() method. It allows you to observe (e.g., log or print) elements as they flow through the pipeline without modifying them.
List<Order> highValueOrders = orders.stream()
.filter(o -> o.getAmount() > 1000)
.peek(o -> log.info("Found high value order: {}", o.getId()))
.collect(Collectors.toList());

  • What factors should you check if processing 10 million records is slow? You should verify:

  • If the logic inside filter or map is too heavy (e.g., network calls or heavy computations).

  • If there are unnecessary operations in the pipeline.

  • If object creation inside the stream is causing high GC overhead.

  • Whether switching to a parallelStream() would help.

  • When should streams be avoided? Streams should be avoided when the logic is highly complex with nested conditionals, as a traditional loop might be easier to read and maintain.

  • What happens if you modify the source collection during stream processing? It can lead to unpredictable results or throw a ConcurrentModificationException. The source data should remain unchanged during processing.

Parallel Streams

  • How does Java decide the number of threads for a parallel stream? It typically depends on the number of available CPU cores in the system (specifically Runtime.getRuntime().availableProcessors()).
  • Why can parallel streams sometimes make performance worse? The overhead of creating, managing, and synchronizing multiple threads can outweigh the benefits of parallelism, especially for very small tasks or operations involving heavy synchronization.
  • Which operations are unsuitable for parallel streams? Operations that modify shared variables, depend on order, or are extremely lightweight. Furthermore, if operations depend on the results of previous elements (like a running total), parallel processing can yield unpredictable results.
  • Which thread pool do parallel streams use? They internally use the ForkJoinPool.commonPool(). Heavily utilizing parallel streams can impact other tasks in the application that rely on this same shared pool.

🛠️ Functional Interfaces & Lambdas

  • What makes an interface functional? It must have exactly one abstract method. However, it can have any number of default or static methods.
@FunctionalInterface
public interface PaymentProcessor {
boolean process(Payment payment); // The single abstract method

default void logTransaction(Payment payment) {
System.out.println("Logging: " + payment.getId());
}
}

  • Why are multiple default methods allowed? Default methods already have an implementation, so they don't interfere with the primary purpose of the interface. The single abstract method rule exists so the compiler knows exactly which method a lambda expression is implementing.

  • Why is the @FunctionalInterface annotation used if it's optional? It provides compile-time safety by triggering a compiler error if someone accidentally adds a second abstract method to the interface.

  • What is the difference between Predicate, Function, Consumer, and Supplier?

  • Predicate: Takes a value and returns a boolean. Predicate<String> isEmpty = String::isEmpty;

  • Function: Takes a value and returns a transformed value. Function<User, String> getName = User::getName;

  • Consumer: Takes a value and performs an action, returning nothing. Consumer<String> print = System.out::println;

  • Supplier: Takes no input but returns a value. Supplier<UUID> idGenerator = UUID::randomUUID;

  • Why must variables inside a lambda be "final" or "effectively final"? To prevent unexpected value changes and ensure thread safety while the lambda is executing. "Effectively final" means the variable isn't explicitly declared with the final keyword, but its value is assigned only once and never changed.


📦 Optionals & Modern Java Features

  • Why was Optional introduced? To handle null values safely, express clear API contracts, and reduce NullPointerExceptions. It acts as a container that clearly indicates a value may or may not be present.
  • What is the difference between orElse and orElseGet?
  • orElse(): Always evaluates and creates the default value, even if the Optional is not empty.
  • orElseGet(): Only evaluates and creates the default value when the Optional is actually empty, making it much more efficient for expensive operations (like DB calls).
// Bad: The DB call happens even if user is found
User user = optionalUser.orElse(userRepository.createDefaultUser());

// Good: The DB call ONLY happens if the optional is empty
User user = optionalUser.orElseGet(() -> userRepository.createDefaultUser());

  • Optional.of vs Optional.ofNullable:
  • Optional.of(): Used when you are certain the value is not null (throws an exception if it is).
  • Optional.ofNullable(): Safer, as it gracefully returns an Optional.empty() if the value is null.

Features Beyond Java 8

  • Java 10 (var): Introduces local variable type inference, allowing the compiler to automatically detect the variable type based on the assigned value.
var userMap = new HashMap<String, User>(); // Type is inferred

  • Java 14 (Records): Special classes designed specifically for immutable data carriers. They automatically generate constructors, getters, toString, equals, and hashCode to reduce boilerplate.
public record UserDto(UUID id, String email, String role) {}

  • Java 17 (Sealed Classes): Restricts which other classes can extend or implement a class, providing better domain modeling and design control.
public sealed interface Event permits LoginEvent, LogoutEvent {}

  • Java 21 (Virtual Threads): Highly lightweight threads managed by the JVM rather than the OS, allowing blocking operations (like database queries or HTTP calls) to scale massively without consuming native OS threads.