Unlock Java 17: Records, Sealed Classes, Pattern Matching and More
This article explores the major Java 17 language enhancements—including records, sealed classes, pattern matching, text blocks, var, and the new switch expression—showing how they reduce boilerplate, improve readability, and boost performance compared to legacy Java 8 code.
From JDK 8 to JDK 17
JDK 17 is not just another version update; it marks a significant shift in the Java platform. As the next long‑term support (LTS) release after JDK 8 and JDK 11, it incorporates every innovation introduced since JDK 9, making it a milestone in Java modernization.
Being an LTS release, JDK 17 will receive at least eight years of support, allowing enterprises to migrate with confidence and enjoy the new features without frequent upgrades. For developers still on JDK 8, jumping directly to JDK 17 is a smart move.
Record Classes
Traditional JavaBeans require a lot of boilerplate code:
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override public boolean equals(Object o) { /* long implementation */ }
@Override public int hashCode() { /* long implementation */ }
@Override public String toString() { return "Person[name=" + name + ", age=" + age + "]"; }
}This verbose code is cumbersome, error‑prone, and hides the class's intent.
JDK 17 introduces record classes, which eliminate the boilerplate entirely:
public record Person(String name, int age) { }The compiler automatically generates the constructor, getters, equals(), hashCode(), and toString() methods. A single line replaces dozens of lines of code.
Records are immutable by design, fitting functional programming principles and aiding thread‑safety. To modify a field, you create a new instance:
Person alice = new Person("Alice", 25);
Person olderAlice = new Person(alice.name(), alice.age() + 1);Use records for DTOs, value objects, or any immutable data container. They cannot extend other classes, declare additional instance fields, or be abstract. If you need those capabilities, stick with a traditional class.
Sealed Classes
Java traditionally offers either final classes (no subclassing) or open classes (any subclass). Sealed classes provide a middle ground, allowing you to specify exactly which classes may extend them.
public sealed class Shape permits Circle, Rectangle, Triangle {
// shared methods and fields
}
public final class Circle extends Shape { }
public sealed class Rectangle extends Shape permits Square { }
public non‑sealed class Triangle extends Shape { }Sealed classes can also be used with interfaces:
public sealed interface Vehicle permits Car, Truck, Motorcycle {
void move();
}They are ideal for domain modeling when you have a closed set of subtypes, enabling the compiler to verify exhaustive switch statements.
Pattern Matching
Before JDK 17, you had to use instanceof followed by an explicit cast:
if (obj instanceof String) {
String s = (String) obj;
// use s
}JDK 17 allows pattern matching directly in the instanceof expression:
if (obj instanceof String s && s.length() > 5) {
// use s directly
}The switch statement also supports pattern matching:
String result = switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
case Person p -> "Person: " + p.name();
default -> "Unknown type";
};Pattern matching improves readability and can lead to performance gains because the compiler can optimize away redundant type checks.
Text Blocks
Before JDK 15, multi‑line strings required cumbersome concatenation:
String html = "<html>
" +
" <body>
" +
" <h1>Hello, World!</h1>
" +
" </body>
" +
"</html>";JDK 17’s text blocks simplify this dramatically:
String html = """
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
""";Text blocks start and end with three double quotes, preserve line breaks, and require no escaping for most characters. They also support whitespace control with \s and line‑continuation using backticks.
var and Enhanced Switch
Although var was introduced in JDK 10, combining it with JDK 17 features yields very concise code:
// Without var
Map<String, List<Person>> groupedPeople = new HashMap<>();
// With var
var groupedPeople = new HashMap<String, List<Person>>();Switch can now be used as an expression and return values directly, optionally using yield for complex logic:
String day = switch (dayOfWeek) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
case 4 -> "Thursday";
case 5 -> "Friday";
case 6, 7 -> "Weekend";
default -> "Invalid date";
};
String result = switch (status) {
case "PENDING" -> {
log.info("Processing pending status");
yield "In progress";
}
case "APPROVED" -> {
log.info("Processing approved status");
yield "Completed";
}
default -> "Unknown status";
};The arrow syntax ( ->) simplifies case bodies, and multiple case labels can be combined.
Other Useful Features
Private interface methods (available since JDK 9) help share implementation details among default methods:
public interface Logger {
default void logInfo(String message) { log(message, "INFO"); }
default void logError(String message) { log(message, "ERROR"); }
private void log(String message, String level) {
System.out.println("[" + level + "] " + message);
}
}Stream API gains convenient toList() and mapMulti methods for more expressive pipelines:
List<String> names = people.stream()
.map(Person::name)
.filter(name -> name.startsWith("Z"))
.toList();
List<String> words = sentences.stream()
.mapMulti((sentence, consumer) -> {
for (String w : sentence.split(" ")) consumer.accept(w);
})
.toList();NullPointerException messages now pinpoint the exact null variable, dramatically easing debugging.
JDK 17 introduces new garbage collectors such as ZGC, which can handle terabyte‑scale heaps with pause times under 10 ms:
-XX:+UseZGCIt also adds the Foreign Memory Access API, allowing safe off‑heap memory manipulation:
try (MemorySegment segment = MemorySegment.allocateNative(100)) {
MemoryAccess.setInt(segment, 0, 42);
int value = MemoryAccess.getInt(segment, 0);
System.out.println(value); // prints 42
}These "magical" JDK 17 features make Java code more concise, readable, and performant, helping developers leave behind the verbosity of older Java versions and embrace modern Java programming.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
