Java has changed more since version 8 than in the decade before it. Knowing which version introduced what matters in interviews ("what's new in Java 17?") and in real projects, because your CI JDK decides which features you can use. This guide walks from Java 8 to Java 21 with code for each feature.

Modern Java Features

Java 8

  • Lambda Expressions — covered in detail above
  • Streams API — covered in detail above
  • Optional<T> — covered above
  • Default & Static methods in interfaces
  • Method references (::)
  • New Date/Time API (java.time)
  • CompletableFuture — async programming

Java 9

// List.of(), Set.of(), Map.of() — immutable collections
List<String> list = List.of("A", "B", "C");  // immutable!
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("A", 1, "B", 2);

// Optional improvements
optional.ifPresentOrElse(v -> use(v), () -> fallback());
optional.or(() -> Optional.of("default"));
optional.stream();

// Private methods in interfaces (Java 9)
interface MyInterface {
    default void methodA() { helper(); }
    default void methodB() { helper(); }
    private void helper() { System.out.println("shared logic"); }
}

Java 10 — var

// var: local variable type inference
var list = new ArrayList<String>();    // ArrayList<String>
var map = new HashMap<String, Integer>(); // HashMap<String,Integer>
var name = "Java 10";               // String

// var in for-each
for (var entry : map.entrySet()) { ... }

// NOT allowed:
// var x;              — must have initializer
// var x = null;       — cannot infer type
// var[] arr;          — can't use with arrays
// class fields        — only local variables
// method parameters   — not allowed
// return types        — not allowed

Java 14-16 — Records & Pattern Matching

// RECORDS (Java 16+): immutable data classes
public record Person(String name, int age) {
    // Automatically generates:
    // - private final fields
    // - canonical constructor
    // - getters: name(), age()
    // - equals(), hashCode(), toString()

    // Custom constructor (compact record constructor)
    public Person {
        if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
        name = name.trim();  // normalize
    }

    // Custom method
    public String greeting() {
        return "Hi, I'm " + name + "!";
    }
}

Person p = new Person("Alice", 30);
p.name();   // Alice
p.age();    // 30
System.out.println(p);  // Person[name=Alice, age=30]

// PATTERN MATCHING instanceof (Java 16+)
Object obj = "Hello World";

// Old way:
if (obj instanceof String) {
    String s = (String) obj;  // explicit cast
    System.out.println(s.length());
}

// New way (pattern matching):
if (obj instanceof String s) {  // auto-cast!
    System.out.println(s.length());  // s is already String here
}

Java 17 — Sealed Classes & Switch Expressions

// SEALED CLASSES: restrict which classes can extend/implement
public sealed class Shape
    permits Circle, Rectangle, Triangle { }

public final class Circle extends Shape {
    double radius;
    Circle(double r) { this.radius = r; }
}

public final class Rectangle extends Shape {
    double width, height;
    Rectangle(double w, double h) { this.width = w; this.height = h; }
}

// Exhaustive switch with sealed hierarchy (pattern matching for switch: Java 21)
double area = switch (shape) {
    case Circle c -> Math.PI * c.radius * c.radius;
    case Rectangle r -> r.width * r.height;
    case Triangle t -> 0.5 * t.base * t.height;
    // No default needed — sealed class is exhaustive!
};

// TEXT BLOCKS (Java 15+) — multiline strings
String json = """
    {
        "name": "Alice",
        "age": 30
    }
    """;

Interview Questions

What is the difference between a Record and a class?

Records are immutable data carriers. They auto-generate constructor, getters (name(), not getName()), equals(), hashCode(), toString(). Cannot extend other classes (implicitly extends Record). Cannot have non-final instance fields. Ideal for DTOs, value objects, and configuration data.

What are sealed classes and why are they useful?

Sealed classes restrict which classes can extend them using permits. This enables exhaustive pattern matching (compiler can verify all cases covered), better domain modeling, and tighter control over class hierarchies. Combined with records and switch expressions, they enable algebraic data types in Java.

Advertisement

Java Platform Module System

// module-info.java — placed in src/main/java/
module com.myapp {
    // Declare dependencies
    requires java.base;          // implicit — always required
    requires java.sql;           // access java.sql package
    requires spring.core;        // spring framework
    requires transitive com.mylib; // re-export: consumers of myapp also get mylib

    // Exports: which packages are accessible to other modules
    exports com.myapp.api;       // public API
    exports com.myapp.model;     // data model
    // com.myapp.internal NOT exported — hidden!

    // Opens: allows deep reflection (needed for frameworks)
    opens com.myapp.model to com.fasterxml.jackson.databind;
    opens com.myapp to spring.core, spring.beans;

    // Services (Service Provider Interface)
    uses com.myapp.spi.PaymentProcessor;   // this module uses this service
    provides com.myapp.spi.PaymentProcessor  // this module provides implementation
        with com.myapp.impl.StripePaymentProcessor;
}

// Benefits of JPMS:
// 1. Strong encapsulation — internal packages truly hidden
// 2. Explicit dependencies — no classpath hell
// 3. Smaller runtime images (jlink) — only include needed modules
// 4. Better security — reflection access controlled

// Build smaller runtime with jlink:
// jlink --module-path $JAVA_HOME/jmods:target/myapp.jar \
//       --add-modules com.myapp \
//       --output custom-runtime
// Result: standalone runtime without full JDK!