JavaJava 8intermediate
Updated:

Java 8 Features: The Complete Practical Guide

6 min read

Every Java 8 feature that still matters in interviews and real projects — lambdas, Streams, Optional, default methods and more — with runnable examples.

TL;DR – Quick Answer

Java 8 introduced lambda expressions, the Stream API, functional interfaces, method references, default and static methods in interfaces, Optional, and a new Date-Time API (java.time). Together these brought functional-style programming to Java and remain the most heavily tested Java topics in interviews today.

On This Page

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, returns boolean. Used by filter.
  • Function<T, R> — takes T, returns R. Used by map.
  • Consumer<T> — takes T, returns nothing. Used by forEach.
  • 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 throws NoSuchElementException. Prefer orElse, orElseThrow with a meaningful exception, or map.

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.
  • StringJoiner and String.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:

  1. Lambda expressions — one day of practice writing them by hand.
  2. Functional interfaces — know the big four cold.
  3. Streams — a week of daily exercises; this is where interviews happen.
  4. Optional — half a day, mostly about avoiding anti-patterns.
  5. 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?
The big four are lambda expressions, the Stream API, functional interfaces (Predicate, Function, Consumer, Supplier), and Optional. Close behind are default and static methods in interfaces, method references, and the java.time Date-Time API. Interviews focus overwhelmingly on lambdas and streams.
Why is Java 8 still asked in interviews when newer versions exist?
Because Java 8 changed how everyday Java code is written. Streams, lambdas and Optional appear in virtually every modern codebase, and later versions build on them rather than replacing them. Interviewers use Java 8 topics to check whether you write modern Java or pre-2014 style Java.
What is the difference between a lambda expression and a method reference?
Both provide an implementation for a functional interface. A lambda writes the logic inline, like s -> s.length(), while a method reference points to an existing method that already has that logic, like String::length. Use a method reference when a method with the exact signature already exists; it reads cleaner.
What problem does Optional solve in Java 8?
Optional makes 'this value may be absent' explicit in the return type instead of relying on null and documentation. A method returning Optional<Customer> forces the caller to handle the empty case, which reduces NullPointerExceptions. It is intended for return types, not for fields or method parameters.
What are default methods and why were they added?
A default method is an interface method with a body, declared with the default keyword. They were added so existing interfaces like List and Collection could gain new methods (such as forEach and stream) without breaking the millions of classes that already implemented them.
Is the old java.util.Date still fine to use after Java 8?
Avoid it in new code. java.util.Date and Calendar are mutable, not thread-safe, and error-prone with months starting at 0. The java.time API (LocalDate, LocalDateTime, ZonedDateTime, Duration, Period) is immutable, thread-safe and far clearer, and has been the recommended choice since 2014.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 14 July 2026 LinkedIn
Chat with us