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
instanceofin 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
| Feature | Finalized in | Replaces / improves |
|---|---|---|
| Records | 16 | hand-written immutable POJOs |
| Sealed classes | 17 | uncontrolled inheritance |
Pattern matching instanceof | 16 | test-then-cast boilerplate |
| Switch expressions | 14 | statement switch, fall-through bugs |
| Text blocks | 15 | multi-line string concatenation |
Best Practices
- Model data with records and constrain hierarchies with sealed types — together they make
switchexhaustive and refactor-safe. - Prefer switch expressions with arrow labels; they eliminate fall-through and force you to handle every case.
- Use the
instanceofbinding 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.