Blog
50 mostly-asked Java 8 interview questions
- January 21, 2026
- Posted by: InterviewExpert.org
- Category: Backend Interview Preparation Java 8
Java 8 Core Concepts (Fundamentals)
1. What are the main features introduced in Java 8?
Some newly added Features of Java 8 are Lambda Expression, Functional Interface, Stream API, Default Method, Method References, Date Time API, Optional Class, Nashorm, and JavaScript Engine.
Short Explanations
- Lambda Expression: lambda expression is a block of code that takes parameters and returns a value. We can refer to Lambda Expression as an object.
- Functional Interfaces: It is an interface that contains only one abstract method.
- Method References: Method References allow us to call the Class method by its name. “::” denotes method reference.
- Default method: It is a method in the interface that implementation is not required in the class implementing that interface.
- Stream API: Stream API is a sequence of objects used to process Collections.
- Date Time API: It is introduced to overcome old Date drawbacks like new Date Time API is thread safe.
- Optional: It is a Wrapper class to check the null value.
- Nashorn, JavaScript Engine: Nashorn is an improved Javascript engine to replace the existing Rhino.
2. What is a Lambda Expression? Explain with syntax and example.
Lambda expression is a function without a name. It is also known as an anonymous function as it does not have type information by itself. We can also say it is a short and concise way to represent a method without a name.
Lambda expressions are mainly used to implement the single abstract method of a functional interface, making your code cleaner, more readable, and more functional in style.
Basic Syntax of Lambda Expression
(parameters) -> { body }Breakdown:
- parameters → input values
- -> → lambda operator (arrow)
- body → logic to execute
Example 1: Lambda with No Parameter
() -> System.out.println("Hello Lambda");Example 2: Lambda with One Parameter
(a) -> System.out.println(a);Parentheses are optional for a single parameter:
a -> System.out.println(a);3. What is a Functional Interface? Give examples.
A Functional Interface in Java is an interface that contains exactly one abstract method, , but it can have any number of default and static methods. It is mainly used to support lambda expressions and method references introduced in Java 8.
Syntax Example (Custom Functional Interface)
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}The @FunctionalInterface annotation is optional, but it ensures compile-time checking.
Using Functional Interface with Lambda Expression
public class Test {
public static void main(String[] args) {
Calculator calc = (a, b) -> a + b;
System.out.println(calc.add(10, 20));
}
}Functional Interface with Default Method
@FunctionalInterface
interface Greeting {
void sayHello();
default void sayBye() {
System.out.println("Bye");
}
}Still a functional interface because it has only one abstract method.
4. What is the Stream API and why is it used?
The Stream API in Java 8 is a feature that allows us to process collections of data in a functional and declarative way.
It provides a stream of elements from a data source (such as a List, Set, or Array) and supports operations like filtering, mapping, sorting, and aggregation.
Why is Stream API Used?
The Stream API is mainly used to:
- Reduce boilerplate code
- Improve readability and maintainability
- Support functional programming
- Enable parallel processing
- Perform bulk operations on collections
Example Without Stream API
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> even = new ArrayList<>();
for (Integer i : list) {
if (i % 2 == 0) {
even.add(i);
}
}
System.out.println(even);Same Example Using Stream API
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> even = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(even);Cleaner, shorter, and more expressive
5. What is Optional and how does it help avoid NullPointerException?
Optional is a class introduced in Java 8 that is used to avoid NullPointerException. It acts as a wrapper which may or may not contain a value.
Instead of returning null, a method returns an Optional, so the developer is forced to check whether a value is present before using it.
Optional Example
Optional<String> name = Optional.ofNullable(getName());
System.out.println(name.orElse("Guest"));Here, if getName() returns null, Optional safely returns "Guest" instead of throwing a NullPointerException.
What Optional Is NOT Meant For?
❌ Not for:
- Class fields
- Method parameters
- Serialization
✔ Best for:
- Method return types
6. What are default methods in interfaces? Why were they introduced?
Default methods are methods in an interface that have a method body. They were introduced in Java 8 using the default keyword. Default methods allow interfaces to have method implementations without forcing implementing classes to override them.
Syntax Example
interface Vehicle {
default void start() {
System.out.println("Vehicle is starting");
}
}Using Default Method
class Car implements Vehicle {
// no need to override start()
}
public class Test {
public static void main(String[] args) {
Car car = new Car();
car.start();
}
}Why Were Default Methods Introduced? (Most Important)
Before Java 8:
- Interfaces could have only abstract methods
- Adding a new method to an interface would break all existing implementations
Java 8 introduced default methods to:
- Maintain backward compatibility
- Add new methods to existing interfaces without breaking code
- Support Stream API and lambda expressions
Real Example (Interview Point)
Java added default methods like forEach() to List and Iterable in Java 8 without breaking old code.
7. What are static methods in interfaces?
Static methods in interfaces are methods that belong to the interface itself, not to the implementing class. They were introduced in Java 8 and are defined using the static keyword.
Static methods in interfaces are utility methods that can be called using the interface name and cannot be overridden. It is basically used for helper functionality.
Syntax Example
interface Calculator {
static int add(int a, int b) {
return a + b;
}
}Calling Static Method
public class Test {
public static void main(String[] args) {
int result = Calculator.add(10, 20);
System.out.println(result);
}
}Must be called using interface name, not object.
Why Static Methods Were Introduced?
- To provide utility/helper methods
- To keep related logic inside the interface
- To avoid creating separate utility classes
- To support Stream API and functional programming
Important Rules (Interview Points)
- Static methods must have a body
- They cannot be overridden by implementing classes
- Called using InterfaceName.method()
- Cannot use
thisorsuper
8. What is a method reference? What are its types?
Method reference is a short and clean way to call an existing method using the :: operator. It is mainly used when a lambda expression only calls one method, so we can replace the lambda with a method reference.
Simple Example
// Lambda
list.forEach(n -> System.out.println(n));
// Method Reference
list.forEach(System.out::println);Types of Method References
1. Static method reference
ClassName::staticMethod2. Instance method of a particular object
object::instanceMethod3. Instance method of an arbitrary object of a class
ClassName::instanceMethod4. Constructor reference
ClassName::new9. Difference between Collection and Stream.
| Collection | Stream |
|---|---|
| Collection is used to store data | Stream is used to process data |
| It holds elements in memory | It does not store elements |
| Can be iterated multiple times | Can be used only once |
| Operations are eager | Operations are lazy |
| Supports add, remove, update | Supports filter, map, reduce |
| Exists since early Java versions | Introduced in Java 8 |
| Follows imperative style | Follows functional style |
10. What is type inference in Java 8?
Type inference means that the Java compiler automatically detects the data type instead of the programmer explicitly specifying it. In Java 8, type inference is mainly used in lambda expressions and method references to reduce boilerplate code.
In Simple we can explain it as Type inference means the compiler figures out the data type automatically, so we don’t need to write it explicitly.
Example Without Type Inference ❌
Comparator<String> c = (String a, String b) -> a.compareTo(b);Example With Type Inference ✅
Comparator<String> c = (a, b) -> a.compareTo(b);Compiler automatically understands that a and b are String.
Type Inference in Lambda Expressions
list.forEach((Integer n) -> System.out.println(n));
// Using type inference
list.forEach(n -> System.out.println(n));11. What are intermediate and terminal operations in Streams?
In the Java Stream API, stream operations are divided into intermediate and terminal operations. They are used together to form a stream pipeline.
Simple Explanation
Intermediate operations prepare the stream, and terminal operations execute it. Intermediate operations are lazy and return a stream, while terminal operations trigger execution and produce a result.
Intermediate Operations
Intermediate operations:
- Return another Stream
- Are lazy (they don’t execute immediately)
- Execute only when a terminal operation is called
Common Intermediate Operations
filter()map()sorted()distinct()limit()
Example
list.stream()
.filter(n -> n > 10)
.map(n -> n * 2);No execution happens yet.
Terminal Operations
Terminal operations:
- Produce a result or side effect
- Trigger the execution of the stream
- End the stream (cannot be reused)
Common Terminal Operations
forEach()collect()count()reduce()findFirst()
Example
list.stream()
.filter(n -> n > 10)
.map(n -> n * 2)
.forEach(System.out::println);Execution starts here.
Key Differences (Interview Table)
| Intermediate | Terminal |
|---|---|
| Returns Stream | Returns result or void |
| Lazy execution | Triggers execution |
| Can be chained | Ends the stream |
| Can have many | Only one per stream |
12. What is a Spliterator?
A Spliterator is a special iterator introduced in Java 8 that is designed to traverse and split elements of a data source (like collections, arrays, or streams), mainly to support efficient parallel processing.
Package: java.util
Interface: Spliterator<T>
Why was Spliterator introduced?
Traditional iterators:
- Traverse elements one by one
- Are mainly designed for sequential processing
Spliterator:
- Improves performance on large data sets
- Can split data into multiple parts
- Enables parallel streams
Spliterator Example:
import java.util.*;
public class SpliteratorExample {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Spliterator<Integer> spliterator = list.spliterator();
Spliterator<Integer> splitPart = spliterator.trySplit();
System.out.println("First part:");
splitPart.forEachRemaining(System.out::println);
System.out.println("Second part:");
spliterator.forEachRemaining(System.out::println);
}
}Output (may vary):
First part:
1
2
Second part:
3
4
5
13. What is Nashorn JavaScript engine?
Nashorn is a JavaScript engine introduced in Java 8 that allows you to run JavaScript code inside Java applications. It was deprecated in Java 11 and removed in Java 15.
Why was Nashorn introduced?
Before Java 8, Java used the Rhino JavaScript engine, which was:
- Slower
- Less optimized for modern JVMs
Nashorn was introduced to:
- Improve performance
- Provide better JVM integration
- Support modern JavaScript (at that time)
Important: Nashorn Status (Very Important for Interviews)
❌ Deprecated in Java 11
❌ Removed in Java 15
So:
- Java 8 → Fully supported
- Java 11 → Deprecated
- Java 15+ → Not available by default
Alternatives to Nashorn
If you need Java + JavaScript today:
- GraalVM JavaScript (recommended)
- Node.js (external)
- ScriptEngine with other languages (Groovy, Kotlin Script, etc.)
14. What is jjs tool in Java 8?
jjs is a command-line JavaScript shell introduced in Java 8 that allows you to execute JavaScript code using the Nashorn JavaScript engine.
15. What are the main functional interfaces in java.util.function?
The package java.util.function provides built-in functional interfaces used heavily with Lambda expressions and Streams.
A functional interface has exactly one abstract method.
Core Functional Interfaces (Most Important)
1. Predicate<T>
Takes one input and returns boolean
boolean test(T t);Use case: Filtering conditions
Predicate<Integer> isEven = n -> n % 2 == 0;2. Function<T, R>
Takes one input, returns one output
R apply(T t);Use case: Transformation / mapping
Function<String, Integer> length = s -> s.length();3. Consumer<T>
Takes one input, returns nothing
void accept(T t);Use case: Performing actions (print, save, log)
Consumer<String> printer = s -> System.out.println(s);4. Supplier<T>
Takes no input, returns output
T get();Use case: Lazy value generation
Supplier<Double> random = () -> Math.random();Two-Argument Functional Interfaces
1. BiPredicate<T, U>
boolean test(T t, U u);BiPredicate<Integer, Integer> greater = (a, b) -> a > b;2. BiFunction<T, U, R>
R apply(T t, U u);BiFunction<Integer, Integer, Integer> sum = (a, b) -> a + b;3. BiConsumer<T, U>
void accept(T t, U u);BiConsumer<String, Integer> print = (s, i) ->
System.out.println(s + " " + i);Stream API & Coding-Based Questions
1. How do you remove duplicates from a list using Streams?
Using distinct()
List<Integer> list = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> uniqueList = list.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueList);Output
[1, 2, 3, 4, 5]
2. How do you separate even and odd numbers using Java 8 Streams?
1. Using partitioningBy()
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Map<Boolean, List<Integer>> result =
numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
List<Integer> evenNumbers = result.get(true);
List<Integer> oddNumbers = result.get(false);
System.out.println("Even: " + evenNumbers);
System.out.println("Odd: " + oddNumbers);Output
Even: [2, 4, 6, 8, 10]
Odd: [1, 3, 5, 7, 9]
2. Using groupingBy()
Map<Boolean, List<Integer>> result =
numbers.stream()
.collect(Collectors.groupingBy(n -> n % 2 == 0));3. How do you count frequency of characters in a string using Streams?
1. Using groupingBy() + counting()
String str = "java stream";
Map<Character, Long> frequencyMap =
str.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c,
Collectors.counting()
));
System.out.println(frequencyMap);Output
{ =1, a=2, j=1, m=1, r=1, s=1, t=1, v=1}
2. Ignore spaces / case-insensitive (Very common follow-up)
String str = "Java Stream";
Map<Character, Long> frequencyMap =
str.toLowerCase()
.chars()
.filter(c -> c != ' ')
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));Output
{a=3, r=1, s=1, t=1, e=1, v=1, j=1, m=1}
4. How do you sort a list in reverse order using Streams?
1. Sort numbers in reverse order
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);
List<Integer> sortedDesc =
numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(sortedDesc);Output
[8, 5, 3, 2, 1]
2. Sort strings in reverse order
List<String> names = Arrays.asList("Java", "Spring", "Stream", "Lambda");
List<String> sortedDesc =
names.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());Output
[Stream, Spring, Lambda, Java]
5. How do you merge two lists and sort them using Streams?
Merge two lists and sort (Natural order)
List<Integer> list1 = Arrays.asList(5, 1, 3);
List<Integer> list2 = Arrays.asList(4, 2, 6);
List<Integer> mergedSorted =
Stream.concat(list1.stream(), list2.stream())
.sorted()
.collect(Collectors.toList());
System.out.println(mergedSorted);Output
[1, 2, 3, 4, 5, 6]
6. How do you filter numbers divisible by 5 using Streams?
1. Basic example (Most common)
List<Integer> numbers = Arrays.asList(10, 12, 15, 18, 20, 22, 25);
List<Integer> divisibleByFive =
numbers.stream()
.filter(n -> n % 5 == 0)
.collect(Collectors.toList());
System.out.println(divisibleByFive);Output
[10, 15, 20, 25]
2. Using IntStream (Primitive stream – better performance)
IntStream.of(10, 12, 15, 18, 20, 22, 25)
.filter(n -> n % 5 == 0)
.forEach(System.out::println);3. From a range of numbers (Interview follow-up)
List<Integer> result =
IntStream.rangeClosed(1, 50)
.filter(n -> n % 5 == 0)
.boxed()
.collect(Collectors.toList());7. How do you convert a list of strings to uppercase using Streams?
List<String> names = Arrays.asList("java", "stream", "lambda");
List<String> upperCaseList =
names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCaseList);Output
[JAVA, STREAM, LAMBDA]
8. How do you find maximum and minimum values using Streams?
List<Integer> numbers = Arrays.asList(10, 25, 5, 40, 15);
Optional<Integer> max =
numbers.stream().max(Integer::compareTo);
Optional<Integer> min =
numbers.stream().min(Integer::compareTo);
System.out.println("Max: " + max.get());
System.out.println("Min: " + min.get());Output
Max: 40
Min: 5
9. How do you group objects by a field using Collectors.groupingBy()?
Employee class
class Employee {
private int id;
private String department;
private double salary;
// getters
}Group employees by department
Map<String, List<Employee>> employeesByDept =
employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));Output
{
IT = [Employee1, Employee2],
HR = [Employee3],
FINANCE = [Employee4]
}
10. How do you convert a list into a map using Collectors.toMap()?
1. Basic example (List → Map)
List<String> names = Arrays.asList("Java", "Spring", "Stream");
Map<String, Integer> map =
names.stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length()
));Output
{Java=4, Spring=6, Stream=6}
2. Convert List of Objects to Map (Most common interview case)
Employee class
class Employee {
private int id;
private String name;
private double salary;
// getters
} Map<Integer, Employee> employeeMap =
employees.stream()
.collect(Collectors.toMap(
Employee::getId,
e -> e
));11. How do you join a list of strings using Collectors.joining()?
1. Basic joining (No delimiter)
List<String> words = Arrays.asList("Java", "Stream", "API");
String result =
words.stream()
.collect(Collectors.joining());
System.out.println(result);Output
JavaStreamAPI
2. Joining with a delimiter (Most common)
String result =
words.stream()
.collect(Collectors.joining(", "));Output
Java, Stream, API
12. How do you find the first non-repeated character in a string using Java 8?
First, we convert the string into a stream of characters, count how many times each character appears, keep the order using LinkedHashMap, and then find the first character with count 1.
String input = "swiss";
Character result = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c,
LinkedHashMap::new,
Collectors.counting()))
.entrySet()
.stream()
.filter(e -> e.getValue() == 1)
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
System.out.println(result); // wOutput
w
s → repeated ❌
w → appears once ✅ (first non-repeated)
13. How do you check if a string is palindrome using Java 8?
String input = "madam";
boolean isPalindrome = IntStream.range(0, input.length() / 2)
.allMatch(i -> input.charAt(i) == input.charAt(input.length() - i - 1));
System.out.println(isPalindrome); // true14. How do you count word occurrences in a sentence using Streams?
String sentence = "java is easy and java is powerful";
Map<String, Long> wordCount = Arrays.stream(sentence.split("\\s+"))
.collect(Collectors.groupingBy(
word -> word,
Collectors.counting()
));
System.out.println(wordCount);Output
{java=2, is=2, easy=1, and=1, powerful=1}
15. How do you sort a map by values using Java 8?
Sort by Values – Ascending
Map<String, Integer> map = new HashMap<>();
map.put("A", 3);
map.put("B", 1);
map.put("C", 2);
Map<String, Integer> sortedMap = map.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
System.out.println(sortedMap);Output
{B=1, C=2, A=3}
Sort by Values – Descending
Map<String, Integer> sortedMap = map.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));Output
{A=3, C=2, B=1}
Functional Interfaces & Lambda Expressions
1. What is the difference between Predicate, Function, Consumer, and Supplier?
One-Line Explanation (Interview Friendly)
- Supplier → Takes no input, returns result
- Predicate → Takes input, returns boolean
- Function → Takes input, returns result
- Consumer → Takes input, returns nothing
Comparison Table (Very Important)
| Interface | Method | Input | Output | Purpose |
|---|---|---|---|---|
Predicate<T> | test(T t) | Yes | boolean | Condition checking |
Function<T,R> | apply(T t) | Yes | R | Transformation |
Consumer<T> | accept(T t) | Yes | void | Perform action |
Supplier<T> | get() | No | T | Provide value |
2. What is BiFunction and BinaryOperator?
Both BiFunction and BinaryOperator are functional interfaces introduced in Java 8. They are used when a lambda expression needs to work with two input values.
BiFunction
BiFunction<T, U, R>
- Takes two inputs
- Returns one result
- Input types and return type can be different
Example
BiFunction<Integer, Integer, String> add =
(a, b) -> "Sum: " + (a + b);
System.out.println(add.apply(10, 20)); // Sum: 30BinaryOperator
BinaryOperator<T>
- Takes two inputs
- Returns a result of the same type
- Special case of
BiFunction<T, T, T>
Example
BinaryOperator<Integer> add = (a, b) -> a + b;
System.out.println(add.apply(10, 20)); // 303. Can we create our own functional interface? How?
Yes, we can create our own functional interface in Java by defining an interface with exactly one abstract method. A custom functional interface is an interface with one abstract method, used to implement lambda expressions.
How to Create a Functional Interface?
- Create an interface
- Add only one abstract method
- (Optional but recommended) Use
@FunctionalInterfaceannotation
Example: Custom Functional Interface
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}Using It with Lambda Expression
public class Test {
public static void main(String[] args) {
Calculator calc = (a, b) -> a + b;
System.out.println(calc.add(10, 20));
}
}4. What is the difference between lambda expression and anonymous class?
Lambda expression is a shorter way to implement a functional interface, while an anonymous class creates a full inner class.
| Lambda Expression | Anonymous Class |
|---|---|
| Introduced in Java 8 | Available since early Java |
| Used only with functional interfaces | Can be used with any interface or abstract class |
| Short and concise syntax | Verbose syntax |
| No method name or class name | Creates an unnamed inner class |
this refers to enclosing class | this refers to anonymous class instance |
| No state (no instance variables) | Can have instance variables |
| Better performance (lightweight) | Slightly heavier |
5. What are effectively final variables in lambda expressions?
In Java, effectively final variables are local variables whose values are assigned only once, even though they are not explicitly declared as final.
Lambda expressions can access local variables only if they are final or effectively final.
Example of Effectively Final Variable
int x = 10; // effectively final
Runnable r = () -> {
System.out.println(x);
};x is not declared final, but since it is not modified, it is effectively final.
Example of NOT Effectively Final ❌
int x = 10;
Runnable r = () -> {
// System.out.println(x);
};
x = 20; // modifiedCompilation error if x is used in lambda.
6. Can lambda expressions throw checked exceptions?
No, lambda expressions cannot throw checked exceptions directly unless the functional interface method declares that exception. It can throw a checked exception only if the functional interface allows it.
Example: Checked Exception ❌ (Compile-Time Error)
Runnable r = () -> {
Thread.sleep(1000); // Checked exception
};Compile-time error because Runnable.run() does not declare throws InterruptedException.
Example: Allowed Checked Exception ✅
@FunctionalInterface
interface Task {
void execute() throws InterruptedException;
}
Task task = () -> {
Thread.sleep(1000); // OK
};7. What is a constructor reference?
A constructor reference is a special type of method reference that refers to a class constructor using the ::new syntax. It is used when a lambda expression only creates a new object.
Syntax of constructor reference
ClassName::newExample: Lambda vs Constructor Reference
Lambda Expression
Supplier<List<String>> list = () -> new ArrayList<>();Constructor Reference
Supplier<List<String>> list = ArrayList::new;Example of constructor reference with Parameters
BiFunction<Integer, String, User> userCreator = User::new;
User user = userCreator.apply(1, "Prakash");8. How do method references improve readability?
Method references improve readability by providing a shorter and clearer syntax when a lambda expression only calls an existing method. Basically Method references remove unnecessary lambda code and clearly show which method is being called.
Example: Lambda vs Method Reference
❌ Lambda Expression
list.forEach(n -> System.out.println(n));✅ Method Reference
list.forEach(System.out::println);The intent is immediately clear: print each element.
Date & Time API (java.time)
1. What problems existed with old Date and Calendar API?
The old Date and Calendar API in Java had several design problems that made it difficult to use. The APIs were mutable, which caused issues in multi-threaded applications and made them not thread-safe.
Another major issue was the confusing API design. Months were zero-based, years were counted from 1900, and many methods were unclear or deprecated, which often led to bugs.
The old APIs also had poor support for date calculations and time zones. Simple operations like adding days or handling different time zones required complex code.
Because of these problems, Java 8 introduced the new Date and Time API (java.time), which is immutable, thread-safe, and easy to use.
2. What is LocalDate, LocalTime, and LocalDateTime?
In Java 8, LocalDate, LocalTime, and LocalDateTime are part of the java.time API. They are immutable, thread-safe classes used to represent date and time without timezone.
LocalDate
LocalDate represents only a date — year, month, and day.
Used when you need just the date (for example: birthday).
LocalDate date = LocalDate.now();LocalTime
LocalTime represents only time — hour, minute, second, nanosecond.
Used when you need just the time (for example: store opening time).
LocalTime time = LocalTime.now();LocalDateTime
LocalDateTime represents both date and time — without timezone.
Used when date and time are required together.
LocalDateTime dateTime = LocalDateTime.now();3. How do you get current date and time in Java 8?
In Java 8, we use the java.time API to get the current date and time. The current date and time are obtained using the now() method of LocalDate, LocalTime, and LocalDateTime.
Get Current Date
LocalDate currentDate = LocalDate.now();Get Current Time
LocalTime currentTime = LocalTime.now();Get Current Date and Time
LocalDateTime currentDateTime = LocalDateTime.now();4. How do you format a date using DateTimeFormatter?
In Java 8, we use DateTimeFormatter (from java.time.format) to format date and time objects like LocalDate, LocalTime, and LocalDateTime into a readable string.
Example
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
String formattedDate = date.format(formatter);
System.out.println(formattedDate);5. How do you calculate difference between two dates in Java 8?
In Java 8, we calculate the difference between two dates using the java.time API, mainly with Period or ChronoUnit. We use Period for years, months, and days, and ChronoUnit for total difference in specific units.
Using Period (Years, Months, Days)
LocalDate startDate = LocalDate.of(2024, 1, 1);
LocalDate endDate = LocalDate.of(2026, 2, 1);
Period period = Period.between(startDate, endDate);
System.out.println(period.getYears()); // 2
System.out.println(period.getMonths()); // 1
System.out.println(period.getDays()); // 0Use Period when you need date-based differences.
Using ChronoUnit (Total Days, Months, etc.)
long days = ChronoUnit.DAYS.between(startDate, endDate);
System.out.println(days);Use ChronoUnit when you need exact units.
6. How do you convert old Date to LocalDate?
In Java 8, we convert the old java.util.Date to LocalDate by using Instant and ZoneId. We first convert Date to Instant, then apply a time zone, and finally convert it to LocalDate.
Code Example
Date date = new Date();
LocalDate localDate = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
System.out.println(localDate);Why ZoneId Is Needed?
Datecontains date + timeLocalDatecontains only date- Time zone is required to correctly convert time to date
Advanced / Experience-Level Questions
1. What is parallel stream? When should you use it?
A parallel stream is a type of Java Stream that processes data in concurrently using multiple threads from the Fork/Join framework.
It helps improve performance by dividing tasks across multiple CPU cores.
How to Create a Parallel Stream
list.parallelStream();or
list.stream().parallel();Code Example
list.parallelStream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);When Should You Use Parallel Streams?
- Large data sets
- CPU-intensive operations
- Independent operations (no shared state)
- Performance testing shows improvement
2. Difference between sequential stream and parallel stream.
Sequential stream processes data in order using one thread, while parallel stream processes data using multiple threads.
| Sequential Stream | Parallel Stream |
|---|---|
| Processes elements one by one | Processes elements simultaneously |
| Uses single thread | Uses multiple threads |
| Maintains encounter order | Order not guaranteed |
| Predictable performance | Performance depends on CPU & data |
| Best for small datasets | Best for large datasets |
| Safer with shared data | Risky with shared mutable data |
3. What is lazy evaluation in Streams?
Lazy evaluation in Java Streams means that stream operations are not executed immediately. They are executed only when a terminal operation is called.
In short we can say Streams don’t process data until a terminal operation is called.
Code Example
list.stream()
.filter(n -> {
System.out.println("Filtering: " + n);
return n > 2;
})
.map(n -> {
System.out.println("Mapping: " + n);
return n * 2;
});No output because there is no terminal operation.
4. What is short-circuiting operation in Streams?
A short-circuiting operation in Java Streams is an operation that stops processing the stream as soon as the result is found, without processing all elements.
Common Short-Circuiting Operations
findFirst()findAny()anyMatch()allMatch()noneMatch()limit()
Code Example
list.stream()
.filter(n -> n > 5)
.findFirst();Stream stops once the first matching element is found.
5. What are side effects in Streams and how to avoid them?
Side effects in Streams occur when a stream operation modifies external state (like changing a variable, collection, or object outside the stream pipeline).
They are dangerous, especially with parallel streams, because they can cause incorrect and unpredictable results.
Side effects should be avoided using collectors and immutable operations.
Example of Side Effect ❌
List<Integer> result = new ArrayList<>();
list.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> result.add(n)); // side effectModifying an external list inside stream.
Why Are Side Effects Bad?
- Break functional programming principles
- Cause thread-safety issues
- Lead to unexpected behavior in parallel streams
How to Avoid Side Effects?
Use Collectors
List<Integer> result = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());Avoid Modifying External Variables
int sum = 0;
list.forEach(n -> sum += n);int sum = list.stream().mapToInt(Integer::intValue).sum();Use Immutable Objects
- Prefer immutable data structures
- Avoid shared mutable state
Important Interview Point
- Side effects are especially dangerous in parallel streams
- Streams should be stateless and non-interfering
6. What is CompletableFuture and how is it used in Java 8?
CompletableFuture is a class introduced in Java 8 for asynchronous programming. It represents a future result of an asynchronous computation and allows us to write non-blocking, reactive code.
It can also define as CompletableFuture is used to run tasks asynchronously and process their results without blocking the main thread.
Basic Example of CompletableFuture
CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> "Hello");
System.out.println(future.get()); // HelloWhy CompletableFuture Was Introduced?
- Overcomes limitations of
Future - Supports chaining of tasks
- Provides better exception handling
- Enables non-blocking execution
Commonly Used Methods
| Method | Purpose |
|---|---|
supplyAsync() | Run task and return result |
runAsync() | Run task without result |
thenApply() | Transform result |
thenAccept() | Consume result |
thenCompose() | Chain async tasks |
exceptionally() | Handle errors |
Chaining Example
CompletableFuture.supplyAsync(() -> 10)
.thenApply(n -> n * 2)
.thenAccept(System.out::println);