Skip to content
Java advanced 4 min read

Java 17 Features

Java 17 (September 2021) is the LTS that consolidated several “Project Amber” features previewed across versions 14–16 into stable, production-ready form. Together, records, sealed classes, and pattern matching reshape how you model data and branch on it — pushing Java toward concise, exhaustive, algebraic-data-style code while staying fully backward compatible. If you skipped 9 through 16, this is the release worth migrating to.

Several features here arrived as previews earlier — records and pattern matching for instanceof in 14/15, sealed classes in 15/16, switch expressions in 12/13. Java 17 finalizes them, so the syntax shown below is permanent.

Records

A record is a transparent carrier for immutable data. One line generates the constructor, private final fields, accessors, plus equals, hashCode, and toString.

public record Point(int x, int y) {
    // Compact constructor for validation
    public Point {
        if (x < 0 || y < 0) throw new IllegalArgumentException("negative");
    }
}

Point p = new Point(3, 4);
System.out.println(p);        // accessor methods are x() and y()
System.out.println(p.x() + p.y());

Output:

Point[x=3, y=4]
7

Records are implicitly final, cannot extend a class, and their fields are immutable — ideal for DTOs, value objects, and pattern-matching targets.

Sealed Classes

A sealed type restricts which classes may extend or implement it, giving you a closed, known hierarchy. This enables exhaustive checking by the compiler.

public sealed interface Shape permits Circle, Rectangle {}

public record Circle(double radius)            implements Shape {}
public record Rectangle(double w, double h)    implements Shape {}

Each permitted subtype must be final, sealed, or non-sealed. Because the set is closed, a switch over Shape can be proven exhaustive without a default branch.

Pattern Matching for instanceof

The type test and the cast collapse into one expression, binding the cast value to a new variable.

Object obj = "hello";

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

// Java 17
if (obj instanceof String s && !s.isBlank()) {
    System.out.println(s.length());   // s is in scope and guarded
}

The binding variable s is also flow-scoped — available only where the test provably holds.

Switch Expressions

switch can now produce a value, uses arrow labels (no fall-through), and is checked for exhaustiveness.

double area = switch (shape) {
    case Circle c    -> Math.PI * c.radius() * c.radius();
    case Rectangle r -> r.w() * r.h();
};   // no default needed — Shape is sealed and covered

Use yield to return a value from a multi-statement block label.

Text Blocks

Multi-line string literals with predictable indentation handling, finalized in Java 15.

String json = """
    {
        "name": "Ada",
        "active": true
    }""";

Incidental leading whitespace (aligned with the closing """) is stripped automatically — no more \n soup or string concatenation for embedded SQL, JSON, or HTML.

Feature Summary

FeatureFinalized inReplaces / improves
Records16hand-written immutable POJOs
Sealed classes17uncontrolled inheritance
Pattern matching instanceof16test-then-cast boilerplate
Switch expressions14statement switch, fall-through bugs
Text blocks15multi-line string concatenation

Best Practices

  • Model data with records and constrain hierarchies with sealed types — together they make switch exhaustive and refactor-safe.
  • Prefer switch expressions with arrow labels; they eliminate fall-through and force you to handle every case.
  • Use the instanceof binding pattern to remove redundant casts, and add && guards inline.
  • Validate invariants in a record’s compact constructor rather than scattering checks across callers.
  • Use text blocks for any literal spanning multiple lines, but keep large fixtures in resource files.

Interview Questions

Q: How does a record differ from a regular class with final fields? A record auto-generates the canonical constructor, accessors, equals, hashCode, and toString based on its components, is implicitly final, and cannot extend another class. It signals “transparent immutable data,” whereas a regular class implies behavior and identity.

Q: Why do sealed classes matter for switch? Because the permitted subtypes are known at compile time, the compiler can verify a switch covers every case and reject it otherwise — giving you exhaustiveness without a fragile default branch that silently swallows new subtypes.

Q: What is flow scoping in pattern matching for instanceof? The binding variable is only in scope where the type test is provably true. In if (o instanceof String s && s.isBlank()), s is usable in the guard and the if body, but not the else.

Q: Are switch expressions exhaustive by default? For sealed types and enums, yes — the compiler checks coverage. For open types like Object, you must supply a default (or case null, default) branch.

Last updated June 1, 2026
Was this helpful?