Java 8 Features
Java 8 (released March 2014) was the most transformative release in Java’s history. It introduced functional programming idioms, a powerful data-processing pipeline, and a long-overdue replacement for the broken Date/Calendar API — all while staying fully backward-compatible.
If you are coming from Java 7 or earlier, this section is your most important stop. If you are already comfortable with Java 8, the deep-dive pages below will fill in the internals you may have skimmed past.

Why Java 8 Mattered
Before Java 8, writing even simple operations like “filter a list and collect results” required verbose anonymous inner classes and explicit loops. Java 8 changed the game with three foundational ideas:
- Lambdas — treat behavior as data, passing code as arguments just like you pass values.
- Streams — a declarative pipeline for processing collections, arrays, and I/O data.
- Functional interfaces — a contract that makes lambdas type-safe and composable.
Everything else in Java 8 builds on, or complements, these three pillars.
A Taste of the Difference
Here is the same task — filter even numbers and print them — written in both styles:
import java.util.Arrays;
import java.util.List;
public class BeforeAndAfter {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// Java 7 style
for (int n : numbers) {
if (n % 2 == 0) System.out.println(n);
}
// Java 8 style
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
}
}
Output:
2
4
6
2
4
6
The Java 8 version reads almost like plain English, and scales to much more complex transformations without losing clarity.
Key Features at a Glance
| Feature | Package / Type | What it solves |
|---|---|---|
| Lambda Expressions | Language syntax | Replaces verbose anonymous classes for single-method interfaces |
| Functional Interfaces | java.util.function | Defines the types that lambdas target (Function, Predicate, etc.) |
| Method References | Language syntax | Shorthand for lambdas that just call an existing method |
| Stream API | java.util.stream | Declarative, lazy, parallelisable data pipelines |
| Optional | java.util.Optional | Eliminates NullPointerException by making absence explicit |
| Default Methods | Language / interfaces | Add new methods to interfaces without breaking existing code |
forEach() on Iterable | java.lang.Iterable | Cleaner iteration using a lambda or method reference |
| New Date/Time API | java.time | Immutable, thread-safe replacement for Date and Calendar |
| Base64 | java.util.Base64 | Built-in encode/decode without third-party libraries |
CompletableFuture | java.util.concurrent | Non-blocking async programming (extends Future) |
| Nashorn JS Engine | javax.script | Embed JavaScript in Java (deprecated in Java 11) |
Note: Java 8 is a Long-Term Support (LTS) release. Many production systems still run on Java 8, so mastering it is essential even if you also work with newer versions.
How Lambdas Connect to Everything Else
It helps to see these features as a dependency tree before diving in:
Lambda Expressions
└─► Functional Interfaces (give lambdas a type)
└─► Method References (shorthand syntax for lambdas)
└─► Stream API (built entirely on functional interfaces)
└─► Collectors (terminal operations for streams)
└─► Default Methods (enabled adding stream() to Collection)
└─► forEach() (iterates with a Consumer lambda)
Optional, Date/Time API, and Base64 are independent features that don’t require lambdas, though they pair naturally with streams.
Under the Hood
Lambdas and invokedynamic
Lambdas are not compiled to anonymous inner classes (a common misconception). The Java compiler emits an invokedynamic bytecode instruction, and at runtime the JVM’s LambdaMetafactory generates the actual implementation class on the fly using MethodHandles. This means:
- The first call has a small one-time setup cost; subsequent calls are as fast as a direct method call.
- No extra
.classfiles are generated at compile time, keeping your JAR smaller. - The JIT compiler can inline lambdas aggressively, often making them zero-overhead compared to explicit loops.
Streams Are Lazy
A stream pipeline does not process any element until a terminal operation (like collect(), forEach(), reduce()) is called. Intermediate operations (filter(), map(), sorted()) build up a pipeline description. This laziness means short-circuiting operations like findFirst() stop processing as soon as they have an answer — no wasted work.
Default Methods and the Diamond Problem
Interfaces can now have concrete default methods. If a class implements two interfaces that both define the same default method, the compiler forces you to override it explicitly — Java resolves the ambiguity at compile time, not runtime, so there is no silent surprises.
java.time and Immutability
All core java.time classes (LocalDate, LocalDateTime, ZonedDateTime, etc.) are immutable and thread-safe. Every “modifier” method (like plusDays()) returns a new object rather than mutating the existing one — the same design as String. This makes date/time arithmetic safe to use in concurrent code without synchronization.
In This Section
- Lambda Expressions — Write concise, anonymous functions with the
->syntax and understand how the JVM handles them. - Functional Interfaces — Explore
Function,Predicate,Consumer,Supplier, and friends fromjava.util.function, and learn how to write your own. - Method References — Use
::to reference static methods, instance methods, or constructors as a cleaner alternative to single-method lambdas. - Stream API — Create and consume data pipelines over collections, arrays, and I/O with
java.util.stream.Stream. - Stream Operations — Deep dive into
filter(),map(),flatMap(),reduce(), and every other intermediate and terminal operation. - Collectors — Master
Collectors.toList(),groupingBy(),joining(),partitioningBy(), and how to build custom collectors. - Optional — Wrap nullable return values safely, chain transformations with
map()/flatMap(), and never write a barenullcheck again. - Default Methods — Add method implementations directly to interfaces for backward-compatible API evolution, and understand the resolution rules.
- forEach() Method — Iterate any
IterableorMapwith a lambda or method reference using theforEach()default method. - Date/Time API (java.time) — Work with
LocalDate,LocalTime,ZonedDateTime,Duration,Period, andDateTimeFormatterin a clean, immutable API. - Base64 Encode/Decode — Encode and decode data to/from Base64 using the built-in
java.util.Base64class without any external dependency.
Quick Compatibility Notes
| Java Version | Status |
|---|---|
| Java 8 | Original release — all features on this page |
| Java 9+ | Streams gained takeWhile(), dropWhile(), iterate() overload |
| Java 10 | var usable in lambda parameters (Java 11) |
| Java 21 | All Java 8 APIs still present; CompletableFuture and streams heavily optimised |
Tip: If you are starting a new project today, target Java 21 (the current LTS), but write code that avoids deprecated APIs. Everything you learn here carries forward with zero changes.
Related Topics
- Lambda Expressions — the cornerstone feature that made Java 8 revolutionary.
- Stream API — process collections declaratively and in parallel with elegant pipelines.
- Functional Interfaces — the type system that makes lambdas and streams type-safe.
- Modern Java (9–21) — explore what came after Java 8, from modules to records to virtual threads.
- Collections Framework — the foundation that the Stream API is built on top of.
- Optional — eliminate
NullPointerExceptionwith a purpose-built wrapper type.