Java 8 Features
Java 8 was the biggest change to the language since generics. Lambda expressions, the Stream API, Optional and the new Date and Time API changed how Java code is written. Learn every major feature with clear explanations and runnable examples.
- Lambda ExpressionsFunctions as values
- Stream APIDeclarative data processing
- OptionalSafer than null
- java.timeModern dates and times
All Java 8 Features at a Glance
Eight areas changed the way Java developers write code. Pick a feature to jump to its explanation and example.
Lambda Expressions
Write behaviour as compact, inline functions.
Functional Interfaces
Ready-made types for lambdas in java.util.function.
Method References
A shorter form of lambdas that call one method.
Stream API
Process collections declaratively: filter, map, group, reduce.
Optional Class
Represent a value that may be absent, without null.
Default and Static Interface Methods
Interfaces can now contain method bodies.
Date and Time API (java.time)
Immutable, thread-safe and easy-to-read dates and times.
More Java 8 Additions
Smaller features you will use every day.
Lambda Expressions
A lambda expression is an anonymous function: a block of code you can pass around like a value. It replaces bulky anonymous classes with a short (parameters) -> expression syntax, which makes sorting, filtering and event handling far more readable.
- Syntax:
(a, b) -> a + b, orx -> { statements; }for a block body - Can only implement a functional interface (one abstract method)
- Can use local variables that are final or effectively final
import java.util.*;
public class LambdaDemo {
public static void main(String[] args) {
List<String> names =
new ArrayList<>(Arrays.asList("Ravi", "Asha", "Meera", "Jo"));
// Before Java 8: anonymous class
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) { return a.compareTo(b); }
});
System.out.println("Anonymous class: " + names);
// Java 8: lambda expression
names.sort((a, b) -> Integer.compare(a.length(), b.length()));
System.out.println("Lambda by length: " + names);
Runnable task = () -> System.out.println("Runnable as a lambda");
task.run();
}
}
Anonymous class: [Asha, Jo, Meera, Ravi] Lambda by length: [Jo, Asha, Ravi, Meera] Runnable as a lambda
Functional Interfaces
A functional interface has exactly one abstract method, so a lambda can implement it. Java 8 added the @FunctionalInterface annotation and the java.util.function package with 43 general-purpose interfaces such as Predicate, Function, Supplier and Consumer.
Predicate<T>: T → boolean (filters and conditions)Function<T,R>: T → R (transformations)Supplier<T>produces values;Consumer<T>accepts them
import java.util.function.*;
public class FunctionalDemo {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> length = s -> s.length();
Supplier<String> greeting = () -> "Hello, Java 8";
Consumer<String> printer = s -> System.out.println("Consumed: " + s);
BinaryOperator<Integer> add = (a, b) -> a + b;
System.out.println("isEven(10) = " + isEven.test(10));
System.out.println("length(\"Kafka\") = " + length.apply("Kafka"));
System.out.println("greeting.get() = " + greeting.get());
printer.accept("Spring Boot");
System.out.println("add(4, 5) = " + add.apply(4, 5));
}
}
isEven(10) = true
length("Kafka") = 5
greeting.get() = Hello, Java 8
Consumed: Spring Boot
add(4, 5) = 9Method References
When a lambda only calls an existing method, a method reference such as String::toUpperCase says the same thing more clearly. There are four kinds: static methods, instance methods of a particular object, instance methods of an arbitrary object, and constructors.
- Static:
Integer::parseInt - Particular object:
System.out::println - Arbitrary object:
String::toUpperCase - Constructor:
User::new
import java.util.*;
import java.util.function.*;
public class MethodRefDemo {
static class User {
private final String name;
User(String name) { this.name = name; }
@Override public String toString() { return "User[" + name + "]"; }
}
public static void main(String[] args) {
List<String> words = Arrays.asList("java", "lambda", "stream");
// instance method of a particular object
words.forEach(System.out::println);
// static method
Function<String, Integer> parse = Integer::parseInt;
// instance method of an arbitrary String
Function<String, String> upper = String::toUpperCase;
// constructor
Function<String, User> create = User::new;
System.out.println(parse.apply("42") + 8);
System.out.println(upper.apply("method references"));
System.out.println(create.apply("Asha"));
}
}
java lambda stream 50 METHOD REFERENCES User[Asha]
Stream API
The Stream API lets you describe what to do with data instead of writing loops. A stream pipeline has a source (such as a list), zero or more intermediate operations (filter, map, sorted) and one terminal operation (collect, count, forEach). Streams are lazy and can run in parallel.
- Intermediate operations are lazy and return a new stream
- Terminal operations trigger processing and produce a result
Collectorsbuilds lists, maps, groups and summaries- Primitive streams (
IntStream) avoid boxing
import java.util.*;
import java.util.stream.*;
public class StreamDemo {
static class Employee {
private final String name, dept;
private final double salary;
Employee(String name, String dept, double salary) {
this.name = name;
this.dept = dept;
this.salary = salary;
}
String getName() { return name; }
String getDept() { return dept; }
double getSalary() { return salary; }
}
public static void main(String[] args) {
List<Employee> staff = Arrays.asList(
new Employee("Asha", "Backend", 120000),
new Employee("Ravi", "Frontend", 95000),
new Employee("Meera", "Backend", 135000),
new Employee("Kiran", "Data", 110000));
List<String> highEarners = staff.stream()
.filter(e -> e.getSalary() > 100000)
.map(Employee::getName)
.sorted()
.collect(Collectors.toList());
System.out.println("Salary > 1,00,000: " + highEarners);
Map<String, Long> perDept = staff.stream()
.collect(Collectors.groupingBy(Employee::getDept,
TreeMap::new, Collectors.counting()));
System.out.println("Count per dept: " + perDept);
double avg = staff.stream()
.mapToDouble(Employee::getSalary)
.average().orElse(0);
System.out.printf("Average salary: %.2f%n", avg);
int sumOfSquares = IntStream.rangeClosed(1, 5).map(n -> n * n).sum();
System.out.println("Sum of squares 1..5: " + sumOfSquares);
}
}
Salary > 1,00,000: [Asha, Kiran, Meera]
Count per dept: {Backend=2, Data=1, Frontend=1}
Average salary: 115000.00
Sum of squares 1..5: 55Optional Class
Optional<T> is a container that either holds a value or is empty. Returning it from a method makes the possibility of "no result" explicit and replaces error-prone null checks with methods such as map, orElse and ifPresent.
- Create with
Optional.of,ofNullableorempty - Read with
orElse,orElseGet,orElseThrow - Best used as a method return type, not for fields or parameters
import java.util.*;
public class OptionalDemo {
static final Map<Integer, String> USERS = new HashMap<>();
static { USERS.put(1, "asha"); USERS.put(2, "ravi"); }
static Optional<String> findUser(int id) {
return Optional.ofNullable(USERS.get(id));
}
public static void main(String[] args) {
String found = findUser(1).map(String::toUpperCase).orElse("GUEST");
String missing = findUser(9).map(String::toUpperCase).orElse("GUEST");
System.out.println(found);
System.out.println(missing);
findUser(2).ifPresent(u -> System.out.println("Found user: " + u));
System.out.println("isPresent(9): " + findUser(9).isPresent());
}
}
ASHA GUEST Found user: ravi isPresent(9): false
Default and Static Interface Methods
Before Java 8, adding a method to an interface broke every class that implemented it. Default methods provide an implementation inside the interface, so libraries could evolve: this is how forEach, stream and removeIf were added to existing collections. Interfaces can also have static helper methods.
defaultmethods are inherited and can be overriddenstaticinterface methods are called on the interface name- Conflicting defaults must be resolved with
Interface.super.method()
public class DefaultMethodDemo {
interface Vehicle {
String name();
// default method: inherited by every implementation
default String describe() { return name() + " is a vehicle"; }
// static method: called on the interface name
static Vehicle of(String name) { return () -> name; }
}
static class Bike implements Vehicle {
public String name() { return "Bike"; }
@Override public String describe() { return "Bike has two wheels"; }
}
public static void main(String[] args) {
System.out.println(Vehicle.of("Car").describe()); // inherited
System.out.println(new Bike().describe()); // overridden
}
}
Car is a vehicle Bike has two wheels
Date and Time API (java.time)
The new java.time package replaced the confusing, mutable Date and Calendar classes. Its types are immutable and clearly named: LocalDate, LocalDateTime, ZonedDateTime, Instant, Duration and Period, with the thread-safe DateTimeFormatter for formatting.
LocalDate: a date without time;LocalDateTime: date and timeZonedDateTimehandles time zones and daylight saving- Months are 1–12 and every operation returns a new object
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Locale;
public class DateTimeDemo {
public static void main(String[] args) {
// Java 8 release date
LocalDate release = LocalDate.of(2014, Month.MARCH, 18);
LocalDate later = release.plusYears(10).plusDays(5);
System.out.println("Java 8 released: " + release
+ " (" + release.getDayOfWeek() + ")");
System.out.println("Plus 10y 5d: " + later);
long days = ChronoUnit.DAYS.between(release, later);
System.out.println("Days between: " + days);
LocalDateTime meeting = LocalDateTime.of(2026, 10, 12, 15, 30);
ZonedDateTime ist = meeting.atZone(ZoneId.of("Asia/Kolkata"));
System.out.println("IST: " + ist);
ZoneId londonZone = ZoneId.of("Europe/London");
ZonedDateTime london = ist.withZoneSameInstant(londonZone);
System.out.println("London: " + london);
DateTimeFormatter fmt =
DateTimeFormatter.ofPattern("dd MMM yyyy, hh:mm a", Locale.ENGLISH);
System.out.println("Formatted: " + meeting.format(fmt));
System.out.println("Duration: " + Duration.ofMinutes(135));
}
}
Java 8 released: 2014-03-18 (TUESDAY) Plus 10y 5d: 2024-03-23 Days between: 3658 IST: 2026-10-12T15:30+05:30[Asia/Kolkata] London: 2026-10-12T11:00+01:00[Europe/London] Formatted: 12 Oct 2026, 03:30 PM Duration: PT2H15M
More Java 8 Additions
Java 8 also brought many smaller improvements: new collection methods, string joining, a standard Base64 encoder, CompletableFuture for asynchronous programming, and more.
Iterable.forEach,Collection.removeIf,Map.mergeandgetOrDefaultString.joinandStringJoinerjava.util.Base64encoder and decoderCompletableFuturefor composing asynchronous tasks
import java.util.*;
import java.util.concurrent.CompletableFuture;
public class MoreFeaturesDemo {
public static void main(String[] args) {
// String.join and StringJoiner
System.out.println(String.join(" | ", "Java", "Spring", "Kafka"));
StringJoiner sj = new StringJoiner(", ", "[", "]");
sj.add("lambda").add("stream");
System.out.println(sj);
// New collection methods: forEach, removeIf, Map.merge, getOrDefault
List<Integer> nums = new ArrayList<>(Arrays.asList(5, 12, 7, 20, 3));
nums.removeIf(n -> n < 6);
System.out.println("removeIf(<6): " + nums);
Map<String, Integer> count = new TreeMap<>();
for (String w : "to be or not to be".split(" ")) {
count.merge(w, 1, Integer::sum);
}
System.out.println("merge counts: " + count);
System.out.println("getOrDefault: " + count.getOrDefault("java", 0));
// Base64
String encoded =
Base64.getEncoder().encodeToString("JavaByTechie".getBytes());
String decoded = new String(Base64.getDecoder().decode(encoded));
System.out.println("Base64: " + encoded + " -> " + decoded);
// CompletableFuture
String result = CompletableFuture.supplyAsync(() -> 21)
.thenApply(n -> n * 2)
.thenApply(n -> "Answer = " + n)
.join();
System.out.println(result);
}
}
Java | Spring | Kafka
[lambda, stream]
removeIf(<6): [12, 7, 20]
merge counts: {be=2, not=1, or=1, to=2}
getOrDefault: 0
Base64: SmF2YUJ5VGVjaGll -> JavaByTechie
Answer = 42Beyond Java 8
Java 8 laid the foundation for modern Java. Later releases built on it with these highlights:
| Version | Key additions |
|---|---|
| Java 9 | Modules, List.of/Map.of, JShell |
| Java 10 | var for local variables |
| Java 11 (LTS) | New String methods, the HTTP Client |
| Java 14 | Switch expressions |
| Java 16 | Records and pattern matching for instanceof |
| Java 17 (LTS) | Sealed classes |
| Java 21 (LTS) | Virtual threads, record patterns, pattern matching for switch |