Skip to content
  • Modern Java: 8 → 21 Feature Tour · Java

Modern Java: 8 → 21 Feature Tour

LearnViaNotes @learnvianotes· 3 min read

What changed from Java 8 to Java 21 — the features interviewers expect you to know and modern codebases actually use.

Feature timeline

VersionHeadline features
8 (2014, LTS)Lambdas, method references, Streams, Optional, java.time, default & static interface methods, CompletableFuture
9Module system (JPMS), List.of / Set.of / Map.of, JShell, private interface methods, Stream.takeWhile, compact strings
10var for local variables, List.copyOf, Collectors.toUnmodifiableList
11 (LTS)String.isBlank/strip/repeat/lines, Files.readString/writeString, standard HttpClient, single-file launch java App.java, var in lambda params
12–13Collectors.teeing, String.indent/transform; switch expressions & text blocks in preview
14Switch expressions (->, yield), helpful NullPointerException messages
15Text blocks """, String.formatted, ZGC production-ready
16Records, pattern matching for instanceof, Stream.toList()
17 (LTS)Sealed classes, strong encapsulation of JDK internals, RandomGenerator
18–20UTF-8 by default, simple web server jwebserver; previews of virtual threads, record patterns
21 (LTS)Virtual threads, pattern matching for switch, record patterns, sequenced collections (getFirst, reversed), generational ZGC

Many features, one program

Java
import java.util.ArrayList;
import java.util.List;

public class Main {
    sealed interface Shape permits Circle, Rect {}             // 17: sealed
    record Circle(double r) implements Shape {}                 // 16: records
    record Rect(double w, double h) implements Shape {}

    static String describe(Object o) {
        return switch (o) {                                     // 21: pattern switch
            case Circle(double r) when r > 10 -> "big circle";  // 21: record pattern + guard
            case Circle c -> "circle r=" + c.r();
            case Rect(double w, double h) -> "rect " + (w * h);
            case String s -> "text of length " + s.length();
            case null -> "null!";
            default -> "something else";
        };
    }

    public static void main(String[] args) {
        var shapes = List.of(new Circle(2), new Rect(3, 4), new Circle(20));   // 10: var, 9: List.of
        shapes.forEach(s -> System.out.println(describe(s)));                 // 8: lambdas
        System.out.println(describe("hi") + ", " + describe(null));

        Object o = shapes.get(1);
        if (o instanceof Rect r && r.w() < r.h()) {                             // 16: instanceof pattern
            System.out.println("tall " + r);
        }

        String json = """
                {"name": "%s", "ok": %b}""".formatted("java", true);          // 15: text block + formatted
        System.out.println(json);

        System.out.println("  hi  ".strip() + "|" + "ab".repeat(3) + "|" + "   ".isBlank());   // 11

        var names = new ArrayList<>(List.of("a", "b", "c"));
        System.out.println(names.getFirst() + names.getLast() + " " + names.reversed());    // 21: sequenced

        List<String> kinds = shapes.stream().map(s -> s.getClass().getSimpleName()).toList();   // 16: toList()
        System.out.println(kinds);
    }
}
Output
circle r=2.0
rect 12.0
big circle
text of length 2, null!
tall Rect[w=3.0, h=4.0]
{"name": "java", "ok": true}
hi|ababab|true
ac [c, b, a]
[Circle, Rect, Circle]

Helpful NullPointerExceptions (14+)

Java
public class Main {
    record Address(String city) {}
    record User(Address address) {}

    public static void main(String[] args) {
        User u = new User(null);
        try {
            System.out.println(u.address().city().length());
        } catch (NullPointerException e) {
            System.out.println(e.getMessage());
        }
    }
}
Output
Cannot invoke "Main$Address.city()" because the return value of "Main$User.address()" is null

Virtual threads (21)

Java
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static void main(String[] args) {
        AtomicInteger done = new AtomicInteger();
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 10_000; i++) {
                executor.submit(() -> {
                    Thread.sleep(10);                 // blocking is cheap on a virtual thread
                    return done.incrementAndGet();
                });
            }
        }                                             // close() waits for all tasks
        System.out.println("completed: " + done.get());
    }
}
Output
completed: 10000

Which Java version would you pick for a new project and why?

What are the headline features of Java 8?

What is pattern matching in Java 21?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report