Java 8 (released in March 2014) is the most consequential release in the language's history. It did not add one feature — it changed the default style of Java from loop-and-mutate to declare-and-transform. More than a decade later, "explain Java 8 features" is still one of the first questions in almost every Java interview, from fresher to 5-plus years.
This guide walks through each feature in the order you should learn them, with runnable code and pointers to the deep-dive pages for the big topics.
The Java 8 feature map
| Feature | What it gives you |
|---|---|
| Lambda expressions | Pass behaviour as a value |
| Functional interfaces | Target types for lambdas |
| Stream API | Declarative collection processing |
| Optional | Explicit "may be absent" values |
| Method references | Shorthand for lambdas that call one method |
| Default/static interface methods | Evolve interfaces without breaking code |
| java.time API | Immutable, sane date-time handling |
| Nashorn, Base64, parallel arrays | Smaller additions |
The first four rows are where interviews live. Learn them in that order — each one builds on the previous.
Lambda expressions: behaviour as a value
Before Java 8, passing behaviour meant writing an anonymous inner class. A lambda expresses the same thing in one line:
import java.util.Arrays;
import java.util.List;
public class LambdaIntro {
public static void main(String[] args) {
List<String> cities = Arrays.asList("Hyderabad", "Pune", "Chennai");
// Java 7 style
cities.sort(new java.util.Comparator<String>() {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
});
// Java 8 style — same result
cities.sort((a, b) -> Integer.compare(a.length(), b.length()));
System.out.println(cities); // [Pune, Chennai, Hyderabad]
}
}
Notice what disappeared: the class declaration, the method signature, the @Override. Only the logic remains. A lambda can only be used where the expected type is a functional interface — an interface with exactly one abstract method. Comparator qualifies, which is why the second version compiles.
The full syntax rules, capture semantics and real use cases are on the lambda expressions page.
Functional interfaces: the target types
Java 8 shipped a toolkit of ready-made functional interfaces in java.util.function so you rarely define your own:
Predicate<T>— takes T, returnsboolean. Used byfilter.Function<T, R>— takes T, returns R. Used bymap.Consumer<T>— takes T, returns nothing. Used byforEach.Supplier<T>— takes nothing, returns T. Used for lazy creation.
These four cover most stream operations, which is why we teach them before streams. See functional interfaces for composition methods like and(), or() and andThen().
The Stream API: declarative data processing
Streams let you say what you want from a collection instead of how to loop over it. Here is a realistic before/after:
import java.util.List;
import java.util.stream.Collectors;
public class StreamIntro {
record Employee(String name, String dept, double salary) {}
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Asha", "Backend", 900_000),
new Employee("Ravi", "Backend", 750_000),
new Employee("Meena", "QA", 650_000)
);
List<String> topBackend = staff.stream()
.filter(e -> e.dept().equals("Backend"))
.filter(e -> e.salary() > 800_000)
.map(Employee::name)
.sorted()
.collect(Collectors.toList());
System.out.println(topBackend); // [Asha]
}
}
Each intermediate operation (filter, map, sorted) returns a new stream and runs lazily; nothing executes until the terminal operation collect runs. Grouping, reducing, parallel streams and the classic pitfalls are covered in Java Streams.
Interview note: "Map vs filter" and "intermediate vs terminal operations" are the two most common Java 8 warm-up questions. Be ready to name three of each category and state that intermediate operations are lazy.
Optional: making absence explicit
Optional<T> is a container that either holds a value or is empty. Its job is to move the "this might not exist" fact from documentation into the type system:
Optional<Employee> lead = staff.stream()
.filter(e -> e.dept().equals("QA"))
.findFirst();
String name = lead.map(Employee::name).orElse("No QA lead");
The caller cannot forget the empty case — the compiler makes it visible. Use it for return types; avoid it for fields and parameters. The Java Optional page covers orElse vs orElseGet and the anti-patterns.
Common mistake: A common mistake beginners make is calling
optional.get()immediately after receiving an Optional. That reintroduces the exact crash Optional was designed to prevent — an empty Optional throwsNoSuchElementException. PreferorElse,orElseThrowwith a meaningful exception, ormap.
Default methods, static methods and method references
Default and static methods in interfaces
Before Java 8, adding a method to an interface broke every implementing class. Default methods solved this:
interface PaymentGateway {
void pay(double amount);
// new method added years later without breaking implementations
default void payWithRetry(double amount, int attempts) {
for (int i = 1; i <= attempts; i++) {
try {
pay(amount);
return;
} catch (RuntimeException e) {
if (i == attempts) throw e;
}
}
}
static PaymentGateway noop() {
return amount -> { /* do nothing */ };
}
}
This is exactly how List.sort(), Collection.stream() and Iterable.forEach() were added to interfaces that existed since the 1990s. Note the diamond question that follows in interviews: if a class implements two interfaces with the same default method, the class must override it (or pick one with InterfaceName.super.method()).
Method references: the cleaner lambda
When a lambda does nothing except call one existing method, replace it with a method reference. There are four kinds:
| Kind | Syntax | Lambda equivalent |
|---|---|---|
| Static method | Integer::parseInt |
s -> Integer.parseInt(s) |
| Instance method of a particular object | logger::info |
msg -> logger.info(msg) |
| Instance method of an arbitrary object | String::toUpperCase |
s -> s.toUpperCase() |
| Constructor | ArrayList::new |
() -> new ArrayList<>() |
The third kind confuses most learners: in String::toUpperCase, the first stream element becomes the receiver (s.toUpperCase()), not an argument.
The java.time Date-Time API
Java 8 replaced the mutable, confusing Date/Calendar pair with the immutable java.time package:
import java.time.LocalDate;
import java.time.Period;
LocalDate courseStart = LocalDate.of(2026, 8, 1);
LocalDate today = LocalDate.now();
Period gap = Period.between(today, courseStart);
System.out.println("Days to batch start: " + gap.getDays());
Key classes: LocalDate (date only), LocalTime, LocalDateTime, ZonedDateTime (with time zone), Duration (machine time) and Period (calendar time). All are immutable and thread-safe — every "modifying" method like plusDays returns a new object.
Smaller Java 8 additions worth naming
java.util.Base64— an official encoder/decoder at last.StringJoinerandString.join()— clean delimited concatenation.Arrays.parallelSort()— fork-join based sorting for large arrays.- Repeating annotations and type annotations.
- Nashorn JavaScript engine — worth knowing it existed and was later removed (deprecated in Java 11, removed in 15), which itself is a common trick question.
You do not need depth on these; naming two or three shows breadth.
Pro tip: When an interviewer says "explain Java 8 features", do not recite all ten. Lead with lambdas and streams, give a one-line code example of each, then say "plus Optional, default methods and the new Date-Time API — happy to go deeper on any." Structure beats coverage.
How to learn Java 8 in the right order
Follow this sequence — it is the same one we use in the Java Full Stack course:
- Lambda expressions — one day of practice writing them by hand.
- Functional interfaces — know the big four cold.
- Streams — a week of daily exercises; this is where interviews happen.
- Optional — half a day, mostly about avoiding anti-patterns.
- Then move forward to Java 17 features — records and sealed classes assume you are fluent in all of the above.
Each of the first four steps has its own deep-dive page linked in the sections above; work through them in order with your IDE open.
Skipping straight to streams without lambdas is the most common self-study mistake we see: you end up copying stream pipelines you cannot modify. Learn the layers in order and each one takes half the time.
Frequently Asked Questions
What are the most important features of Java 8?
Why is Java 8 still asked in interviews when newer versions exist?
What is the difference between a lambda expression and a method reference?
What problem does Optional solve in Java 8?
What are default methods and why were they added?
Is the old java.util.Date still fine to use after Java 8?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

